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

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

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

注解

本章提到了一些包含编码后的 Python 版本号的文件名。这些文件名用版本号 XY 表示;实际上,'X' 将是您使用的 Python 版本的主版本号,而 'Y' 将是次版本号。例如,如果您使用 Python 2.2.1,则 XY 实际上将是 22

5.1. 实用方法

与 Unix 一样,在 Windows 上构建扩展模块有两种方法:使用 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 添加到库列表中。