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. 简易指南¶
在 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.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 *
;它确实会创建单独的副本。
-
Py_NO_LINK_LIB¶
关闭 CPython 头文件中执行的与 Python 库的隐式、基于
#pragma
的链接。在 3.14 版本加入。
5.3. DLLs 的实际使用¶
Windows Python 是用 Microsoft Visual C++ 构建的;使用其他编译器可能有效也可能无效。本节的其余部分是 MSVC++ 特有的。
在 Windows 中创建 DLL 时,您可以通过两种方式使用 CPython 库:
默认情况下,直接或通过
Python.h
包含PC/pyconfig.h
会触发与库的隐式、配置感知链接。头文件为 Debug 选择pythonXY_d.lib
,为 Release 选择pythonXY.lib
,以及为启用了 有限 API 的 Release 选择pythonX.lib
。要构建两个 DLL,spam 和 ni(使用 spam 中的 C 函数),您可以使用以下命令:
cl /LD /I/python/include spam.c cl /LD /I/python/include ni.c spam.lib
第一个命令创建了三个文件:
spam.obj
、spam.dll
和spam.lib
。Spam.dll
不包含任何 Python 函数(例如PyArg_ParseTuple()
),但由于隐式链接的pythonXY.lib
,它知道如何找到 Python 代码。第二个命令创建了
ni.dll
(以及.obj
和.lib
),它知道如何从 spam 以及 Python 可执行文件中找到必要的函数。通过在包含
Python.h
之前定义Py_NO_LINK_LIB
宏手动链接。您必须将pythonXY.lib
传递给链接器。要构建两个 DLL,spam 和 ni(使用 spam 中的 C 函数),您可以使用以下命令:
cl /LD /DPy_NO_LINK_LIB /I/python/include spam.c ../libs/pythonXY.lib cl /LD /DPy_NO_LINK_LIB /I/python/include ni.c spam.lib ../libs/pythonXY.lib
第一个命令创建了三个文件:
spam.obj
、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。要去除它们,请使用“项目设置”对话框中的“链接”选项卡,指定 忽略默认库。将正确的 msvcrtxx.lib
添加到库列表中。