5. 在Windows平台编译C和C++扩展
鼓励模块作者使用 distutils 方式来构建扩展模块,而不使用本节所描述的方式。 你仍将需要使用 C 编译器来构建 Python;通常为 Microsoft Visual C++。
注解
这一章提及了多个包括已编码 Python 版本号的文件名。 这些文件名以显示为 的版本号来代表;在实践中,'X'
将为你所使用的 Python 发布版的主版本号而 'Y'
将为次版本号。 例如,如果你所使用的是 Python 2.2.1,XY
将为 22
。
在 Windows 和 Unix 上构建扩展模块都有两种方式:使用 distutils 包来控制构建过程,或者全手动操作。 distutils 方式适用于大多数扩展;使用 构建和打包扩展模块的文档见 分发 Python 模块(遗留版本)。 如果你发现你确实需要手动操作,那么研究一下 标准库模块的项目文件可能会很有帮助。
Unix 和 Windows 对于代码的运行时加载使用了完全不同的范式。 在你尝试构建可动态加载的模块之前,要先了解你所用系统是如何工作的。
在 Windows 中,一个动态链接库 (.dll
) 文件中没有悬挂的引用。 而是通过一个查找表执行对函数或数据的访问。 因此在运行时 DLL 代码不必在运行时进行修改;相反地,代码已经使用了 DLL 的查找表,并且在运行时查找表会被修改以指向特定的函数和数据。
在 Unix 中,只存在一种库文件 (.a
),它包含来自多个对象文件 (.o
) 的代码。 在创建共享对象文件 (.so
) 的链接阶段,链接器可能会发现它不知道某个标识符是在哪里定义的。 链接器将在各个库的对象文件中查找它;如果找到了它,链接器将会包括来自该对象文件的所有代码。
在 Windows 中,存在两种库类型,静态库和导入库 (扩展名都是 .lib
)。 静态库类似于 Unix 的 文件;它包含在必要时可被包括的代码。 导入库基本上仅用于让链接器能确保特定标识符是合法的,并且将在 DLL 被加载时出现于程序中。 这样链接器可使用来自导入库的信息构建查找表以便使用未包括在 DLL 中的标识符。 当一个应用程序或 DLL 被链接时,可能会生成一个导入库,它将需要被用于应用程序或 DLL 中未来所有依赖于这些符号的 DLL。
假设你正在编译两个动态加载模块 B 和 C,它们应当共享另一个代码块 A。 在 Unix 上,你 不应 将 A.a
传给链接器作为 B.so
和 C.so
;那会导致它被包括两次,这样 B 和 C 将分别拥有它们自己的副本。 在 Windows 上,编译 A.dll
将同时编译 A.lib
。 你 应当 将 A.lib
传给链接器用于 B 和 C。 A.lib
并不包含代码;它只包含将在运行时被用于访问 A 的代码的信息。
在 Windows 上,使用导入库有点像是使用 import spam
;它让你可以访问 spam 中的名称,但并不会创建一个单独副本。 在 Unix 上,链接到一个库更像是 from spam import *
;它会创建一个单独副本。
当在 Windows 中创建 DLL 时,你必须将 pythonXY.lib
传给链接器。 要编译两个 DLL,spam 和 ni (会使用 spam 中找到的 C 函数),你应当使用以下命令:
第一个命令创建了三个文件: , spam.dll
和 spam.lib
。 Spam.dll
不包含任何 Python 函数 (例如 PyArg_ParseTuple()),但它通过 pythonXY.lib
可以知道如何找到所需的 Python 代码。
第二条命令创建了 ni.dll
(以及 .obj
和 .lib
),它知道如何从 spam 以及 Python 可执行文件中找到所需的函数。
不是每个标识符都会被导出到查找表。 如果你想要任何其他模块(包括 Python)都能看到你的标识符,你必须写上 _declspec(dllexport)
,就如在 void _declspec(dllexport) initspam(void)
或 PyObject _declspec(dllexport) *NiGetSpamData(void)
中一样。
Developer Studio 将加入大量你并不真正需要的导入库,使你的可执行文件大小增加 100K。 要摆脱它们,请使用项目设置对话框的链接选项卡指定 忽略默认库。 将正确的 添加到库列表中。