在 Windows 上构建 C 和 C++ 扩展
*******************************

这一章简要介绍了如何使用 Microsoft Visual C++ 创建 Python 的 Windows
扩展模块，然后再提供有关其工作机理的详细背景信息。这些说明材料同时适用
于 Windows 程序员学习构建 Python 扩展以及 Unix 程序员学习如何生成在
Unix 和 Windows 上均能成功构建的软件。

鼓励模块作者使用 distutils 方式来构建扩展模块，而不使用本节所描述的方
式。你仍将需要用来构建 Python 的那个 C 编译器；通常为 Microsoft Visual
C++.

备注:

  这一章提及了多个包括已编码 Python 版本号的文件名。这些文件名以显示为
  "XY" 的版本号来代表；在实践中，"'X'" 将为你所使用的 Python 发布版的
  主版本号而 "'Y'" 将为次版本号。例如，如果你所使用的是 Python 2.2.1，
  "XY" 将为 "22"。


菜谱式说明
==========

与在 Unix 上一样，在 Windows 上构造扩展模块也有两种方式：使用
"setuptools" 包来控制构建过程，或者全手动操作。 setuptools 方式适用于
大多数扩展；使用 "setuptools" 构建和打包扩展模块的文档见 使用
setuptools 构建 C 和 C++ 扩展。如果你发现你真的需要手动操作，那么研究
一下 winsound 标准库模块的项目文件可能会很有帮助。


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 头文件中执行的基于 "#pragma" 的与 Python 库的隐式链
   接。

   Added in version 3.14.


DLL 的实际使用
==============

Windows Python 是在 Microsoft Visual C++ 中构建的；使用其他编译器可能
会也可能不会工作。本节的其余部分是针对 MSVC++ 的。

在 Windows 中创建 DLL 时，你可以通过两种方式使用 CPython 库：

1. 默认情况下，直接包含 "PC/pyconfig.h" 或通过 "Python.h" 会触发与库的
   隐式、配置感知型的链接。 头文件选择 "pythonXY_d.lib" 用于调试，
   "pythonXY.lib" 用于发布，以及 "pythonX.lib" 用于启用了 受限 API 的
   发布。

   要构建两个 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 可执行文件中找到所需的函数。

2. 在包含 "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。 要摆脱它们，请使用项目设置对话框中的链接选项卡指定 *
忽略默认库*。将正确的 "msvcrt*xx*.lib" 添加到库列表中。
