CPython Windows 扩展模块构建指南:MSVC 编译、DLL 导入库与 Py_NO_LINK_LIB 机制
发布时间:2026/9/7 23:35:36 作者:尧图编辑部 阅读量:1,286

CPython Windows 扩展模块构建指南MSVC 编译、DLL 导入库与 Py_NO_LINK_LIB 机制【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇基于 CPython 仓库官方文档 Doc/extending/windows.rst 展开系统讲解在 Windows 上使用 Microsoft Visual C 构建 Python C/C 扩展模块的完整方法包括 Unix 与 Windows 动态链接范式的本质差异、cl编译器的两条链接路径隐式链接与Py_NO_LINK_LIB手动链接以及符号导出和导入库裁剪等实战技巧。读完本文你将能够独立在 Windows 上编译出可被 Python 加载的.pyd即 DLL扩展模块并理解 PC/pyconfig.h 中自动选择pythonXY.lib的底层逻辑。扩展模块的两种构建方式文档开篇即给出两条路线这与 Unix 平台上的做法一一对应推荐路线用setuptools控制构建过程。对绝大多数扩展模块setuptools方案工作良好。你仍然需要当初构建 Python 所用的 C 编译器通常是 Microsoft Visual C。手动路线直接调用编译器。如果确有需要手动操作文档建议研究标准库winsound模块的工程文件 PCbuild/winsound.vcxproj 作为参照。该工程文件引用了 PC/winsound.c 作为编译目标见工程内ClCompile Include..\PC\winsound.c /是 CPython 仓库中现成的 Windows 扩展模块范例。文档还提醒模块作者应优先采用distutils/setuptools方式构建扩展模块而不是手写编译命令。关于版本号约定文档中出现的文件名包含编码后的 Python 版本号XY其中X为主版本号、Y为次版本号例如 Python 2.2.1 对应22。就本仓库当前代码而言Include/patchlevel.h 中定义为PY_MAJOR_VERSION 3、PY_MINOR_VERSION 16因此在当前主线分支上XY即为316对应python316.lib、python316_d.lib等文件。Unix 与 Windows 的动态链接差异文档强调Unix 与 Windows 在运行时加载代码上采用完全不同的范式构建任何动态加载模块之前必须先理解本系统的机制。共享对象.so与动态链接库.dllUnix共享对象.so文件既包含供程序使用的代码也包含它所期望在宿主程序中找到的一组函数和数据的名字。当.so被并入程序时文件中所有对这些函数和数据的引用都会被改写指向函数和数据在内存中的实际位置——这本质上就是一次链接操作运行时重定位。Windows.dll文件不存在任何悬空引用。对函数或数据的访问通过一张查找表完成DLL 代码本身无需在运行时修正以指向宿主程序内存代码一开始就通过 DLL 的查找表访问运行时被修改的是查找表本身让它指向真正的函数和数据。静态库与导入库都是 .libUnix只有一种库文件.a它包含多个目标文件.o中的代码。生成.so的链接阶段链接器遇到无法解析的标识符时会去各库的目标文件中查找一旦找到就会把该目标文件的全部代码包含进来。Windows有两种库静态库和导入库扩展名都是.lib。静态库类似 Unix 的.a包含按需引入的代码导入库的作用基本只是“安抚”链接器——确认某个标识符是合法的、且 DLL 加载时必然存在于程序里。链接器利用导入库中的信息为 DLL 自身不包含的标识符构建查找表。当某个应用程序或 DLL 被链接时可能生成一个导入库所有未来依赖这些符号的 DLL 都必须使用它。文档用一个共享代码块 A 的经典例子说明了两者的关键区别假设要构建两个动态加载模块 B 和 C它们都依赖另一块代码 A。在Unix上你不会把A.a传给B.so和C.so的链接器——否则 A 会被包含两次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 3.14 起引入Py_NO_LINK_LIB关闭 CPython 头文件内部通过隐式#pragma机制与 Python 库的链接行为。在源码层面该机制的完整实现位于 PC/pyconfig.hWindows 平台编译时生效的头文件逻辑如下/* Automatic linking of extension python3x.lib files for MSVC DLLs. This lets MSVC users build extensions without manually specifying .lib files. Define Py_NO_LINK_LIB to disable this behavior. */ #if !defined(Py_NO_LINK_LIB) \ defined(_MSC_VER) defined(Py_ENABLE_SHARED) \ !defined(Py_BUILD_CORE) !defined(Py_BUILD_CORE_BUILTIN) /* not building the core - must be an ext */ # if defined(Py_GIL_DISABLED) # if defined(Py_DEBUG) # pragma comment(lib,python316t_d.lib) # elif defined(Py_LIMITED_API) || defined(Py_TARGET_ABI3T) # pragma comment(lib,python3t.lib) # else # pragma comment(lib,python316t.lib) # endif /* Py_DEBUG */ # else # if defined(Py_DEBUG) # pragma comment(lib,python316_d.lib) # elif defined(Py_TARGET_ABI3T) # pragma comment(lib,python3t.lib) # elif defined(Py_LIMITED_API) # pragma comment(lib,python3.lib) # else # pragma comment(lib,python316.lib) # endif /* Py_DEBUG */ # endif /* Py_GIL_DISABLED */ #endif从这段源码结构看自动链接有四个前提条件未定义Py_NO_LINK_LIB、使用 MSVC_MSC_VER、启用了共享核心Py_ENABLE_SHARED见同文件 PC/pyconfig.h 中Py_NO_ENABLE_SHARED的处理、并且不是在构建核心本身排除Py_BUILD_CORE/Py_BUILD_CORE_BUILTIN。在此基础上头文件按构建配置自动选择导入库构建配置选中的导入库本仓库 3.16 主线为例Debugpython316_d.libRelease默认python316.libRelease Limited APIPy_LIMITED_APIpython3.lib不带版本号的稳定 ABI 导入库GIL 禁用构建Py_GIL_DISABLEDpython316t_d.lib/python3t.lib/python316t.lib带t后缀这就是文档所说“头文件为 Debug 选pythonXY_d.lib、Release 选pythonXY.lib、启用 Limited API 的 Release 选pythonX.lib”的实现细节。实战在 Windows 上使用 DLL文档提醒Windows 版 Python 使用 Microsoft Visual C 构建使用其他编译器可能有效也可能无效以下内容为 MSVC 专属。在 Windows 上创建 DLL 时使用 CPython 库有两种方式方式一默认隐式链接包含PC/pyconfig.h直接或间接经由Python.h会触发一次隐式的、配置感知的库链接。构建两个 DLL——spam和依赖 spam 中 C 函数的ni——可用以下命令cl /LD /I/python/include spam.c cl /LD /I/python/include ni.c spam.lib其中/I参数应指向你的 Python 安装头文件目录/LD表示生成 DLL。第一条命令生成三个文件spam.obj、spam.dll、spam.lib。spam.dll本身不包含任何 Python 函数如PyArg_ParseTuple但正是由于隐式链接了pythonXY.lib它知道如何找到 Python 代码。第二条命令生成ni.dll以及.obj和.lib它知道如何找到 spam 中以及 Python 可执行文件中需要的函数。方式二定义 Py_NO_LINK_LIB 手动链接在包含Python.h之前定义Py_NO_LINK_LIB宏通过/D传入预处理器此时你必须自行把pythonXY.lib传给链接器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随后是ni.dll区别仅在于 Python 库导入库的来源从头文件里的#pragma comment变成了命令行上的显式参数。方式二的好处是链接目标完全透明、可被脚本精确控制代价是必须保证.lib路径正确。导出你自己的符号并非所有标识符都会被导出到查找表。如果你希望其他模块包括 Python 本体能看到你的标识符必须显式标注_declspec(dllexport)例如void _declspec(dllexport) initspam(void) PyObject _declspec(dllexport) *NiGetSpamData(void)注意 Windows 的 CPython 通过__declspec处理符号可见性PC/pyconfig.h 中对所有使用该头文件的 Windows 编译器统一定义了HAVE_DECLSPEC_DLL配合 Include/pyport.h 中的PyAPI_FUNC/PyAPI_DATA等宏完成核心的符号导出。裁剪多余的默认导入库文档最后给出了一个实用技巧Developer Studio 会塞进大量你根本不需要的导入库给可执行文件凭空增加约 100K。去除方法打开 Project Settings 对话框的 Link 选项卡勾选ignore default libraries并在库列表中添加正确的msvcrt{xx}.libCRT 导入库让链接器只保留必需的那一个。配套参考从仓库内查看构建细节标准库winsound的 Windows 扩展模块工程文件PCbuild/winsound.vcxproj可对照其ClCompile、Link与附加库配置理解手动构建参数。CPython 在 Windows 上自身的构建说明MSVC 工作负载、build.bat、Release/Debug 配置、_d后缀约定、Clang/LLVM 备选工具链等PCbuild/readme.txt。其中 Debug 配置会在二进制名中加入_d如python_d.exe与本文 Debug 模式选择pythonXY_d.lib的规则相互呼应。跨平台扩展模块构建总览setuptools/distutils 路线、构建选项Doc/extending/building.rst。最小可运行的扩展模块教程模块样板代码、PyInit_xxx入口Doc/extending/first-extension-module.rst。小结在 Windows 上构建 CPython 扩展模块的核心心智模型是.pyd就是一份 DLL它不重定位、而是通过导入库.lib构建的查找表在运行时定位 Python 核心符号CPython 头文件默认替你完成这一步按 Debug/Release/Limited API/GIL 状态自动选择pythonXY.lib而Py_NO_LINK_LIB让你拿回控制权、显式指定导入库。配合_declspec(dllexport)导出自己的符号、以及ignore default libraries裁剪冗余 CRT 库即可产出一个尺寸干净、链接关系明确的 Windows 扩展模块。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考