5. 在 Windows 上构建 C 和 C++ 扩展

本章简要介绍了如何使用 Microsoft Visual C++ 为 Python 创建 Windows 扩展模块,并接着提供了有关其工作原理的更详细的背景信息。这些解释性材料对学习构建 Python 扩展的 Windows 程序员和有兴趣制作可以在 Unix 和 Windows 上成功构建的软件的 Unix 程序员都很有用。

鼓励模块作者使用 distutils 方法来构建扩展模块,而不是本节中描述的方法。您仍然需要用于构建 Python 的 C 编译器;通常是 Microsoft Visual C++。

注意

本章提到了许多文件名,其中包含编码的 Python 版本号。这些文件名用版本号表示,显示为 XY;在实践中,'X' 将是主要版本号,而 'Y' 将是您正在使用的 Python 版本的次要版本号。例如,如果您使用的是 Python 2.2.1,则 XY 实际上将是 22

5.1. 食谱方法

在 Windows 上构建扩展模块有两种方法,就像在 Unix 上一样:使用 setuptools 包来控制构建过程,或者手动进行。setuptools 方法适用于大多数扩展;有关使用 setuptools 构建和打包扩展模块的文档可在 使用 setuptools 构建 C 和 C++ 扩展 中找到。如果您发现您确实需要手动操作,那么研究 winsound 标准库模块的项目文件可能会有所帮助。

5.2. Unix 和 Windows 之间的差异

Unix 和 Windows 使用完全不同的范式来进行代码的运行时加载。在尝试构建可以动态加载的模块之前,请了解您的系统的工作原理。

在 Unix 中,共享对象 (.so) 文件包含要由程序使用的代码,以及它期望在程序中找到的函数和数据的名称。当该文件与程序连接时,文件代码中对这些函数和数据的引用都会更改为指向程序中函数和数据在内存中的实际位置。这基本上是一个链接操作。

在 Windows 中,动态链接库(.dll)文件没有悬挂引用。相反,对函数或数据的访问通过查找表进行。因此,DLL 代码在运行时不需要修复以引用程序的内存;相反,代码已经使用 DLL 的查找表,并且查找表在运行时被修改以指向函数和数据。

在 Unix 中,只有一种类型的库文件(.a),它包含来自多个目标文件(.o)的代码。在创建共享对象文件(.so)的链接步骤中,链接器可能会发现它不知道某个标识符在哪里定义。链接器将在库中的目标文件中查找它;如果找到它,它将包含来自该目标文件的所有代码。

在 Windows 中,有两种类型的库,静态库和导入库(两者都称为 .lib)。静态库类似于 Unix .a 文件;它包含要根据需要包含的代码。导入库基本上只用于向链接器保证某个标识符是合法的,并且在加载 DLL 时将在程序中存在。因此,链接器使用来自导入库的信息来构建查找表,以使用未包含在 DLL 中的标识符。当应用程序或 DLL 被链接时,可能会生成一个导入库,该库将需要用于所有将来依赖于应用程序或 DLL 中符号的 DLL。

假设您正在构建两个动态加载模块 B 和 C,它们应该共享另一个代码块 A。在 Unix 中,您不会A.a 传递给 B.soC.so 的链接器;这会导致它被包含两次,因此 B 和 C 将分别拥有自己的副本。在 Windows 中,构建 A.dll 也会构建 A.lib。您确实A.lib 传递给 B 和 C 的链接器。 A.lib 不包含代码;它只包含在运行时用于访问 A 的代码的信息。

在 Windows 中,使用导入库有点类似于使用 import spam;它允许您访问 spam 的名称,但不会创建单独的副本。在 Unix 中,使用库链接更像是 from spam import *;它确实创建了一个单独的副本。

5.3. 在实践中使用 DLL

Windows Python 是在 Microsoft Visual C++ 中构建的;使用其他编译器可能有效也可能无效。本节的其余部分是针对 MSVC++ 的。

在 Windows 中创建 DLL 时,您必须将 pythonXY.lib 传递给链接器。要构建两个 DLL,spam 和 ni(它使用在 spam 中找到的 C 函数),您可以使用以下命令

cl /LD /I/python/include spam.c ../libs/pythonXY.lib
cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib

第一个命令创建了三个文件:spam.objspam.dllspam.libSpam.dll 不包含任何 Python 函数(例如 PyArg_ParseTuple()),但它确实知道如何找到 Python 代码,这要归功于 pythonXY.lib

第二个命令创建了 ni.dll(以及 .obj.lib),它知道如何从 spam 以及 Python 可执行文件中找到必要的函数。

并非所有标识符都被导出到查找表。如果您希望任何其他模块(包括 Python)能够看到您的标识符,您必须说 _declspec(dllexport),例如 void _declspec(dllexport) initspam(void)PyObject _declspec(dllexport) *NiGetSpamData(void)

Developer Studio 会添加许多您实际上不需要的导入库,从而为您的可执行文件增加大约 100K。要摆脱它们,请使用“项目设置”对话框的“链接”选项卡,指定忽略默认库。将正确的 msvcrtxx.lib 添加到库列表中。