杰瑞科技汇

Windows下Python C扩展如何开发?

这个过程在 Windows 上比在 Linux 上稍微复杂一些,主要是因为编译和链接环境的配置,我们将使用最主流和推荐的方法:Microsoft Visual C++ (MSVC) 编译器,并结合 Python 的官方构建工具。

核心概念

  1. 为什么需要 C 扩展?

    • 性能瓶颈:对计算密集型任务(如数值计算、图像处理)进行优化。
    • 封装现有库:将用 C/C++ 编写的高质量库(如数据库驱动、科学计算库)提供给 Python 调用。
    • 与底层系统交互:访问 Python 无法直接触及的操作系统 API 或硬件。
  2. 工作流程

    • 编写 C/C++ 代码:实现你的核心逻辑,并使用 Python C API 来创建 Python 可调用的对象(函数、类等)。
    • 编写 setup.py 文件:这是构建脚本,它告诉 Python 的构建工具(setuptools)如何编译你的 C 代码,需要哪些库和头文件。
    • 编译和链接:使用 MSVC 编译器将 C 代码编译成目标文件(.obj),然后链接成 Python 可加载的动态链接库(在 Windows 上是 .pyd 文件)。
    • 安装和测试:将生成的 .pyd 文件安装到你的 Python 环境中,然后在 Python 代码中 import 并使用它。

环境准备

在开始之前,确保你的系统已经准备好必要的工具。

安装 Python

Python 官网 下载并安装 Python。请务必勾选 "Add Python to PATH" 选项,这会让命令行工具更容易找到 Python。

安装 C 编译器 (MSVC)

这是最关键的一步,Python 的官方二进制版本是使用 MSVC 编译的,因此你的 C 扩展也必须使用 MSVC 来编译,以确保二进制兼容性。

  • 如果你安装了 Visual Studio:确保在安装时勾选了 "使用 C++ 的桌面开发" 工作负载,这会自动安装 MSVC 编译器和 Windows SDK。
  • 如果你没有安装 Visual Studio:微软提供了独立的 "Build Tools for Visual Studio",这是更轻量级的选择。
    1. 访问 Visual Studio Build Tools 下载页面
    2. 下载并运行安装程序。
    3. 在 "工作负载" 选项卡中,选择 "使用 C++ 的桌面开发"
    4. 在右侧的 "安装详细信息" 中,确保默认的 MSVC 工具包和 Windows SDK 已被选中。
    5. 点击 "安装"。

安装完成后,你需要打开一个 "开发者命令提示符",这个特殊的命令行环境已经预先配置好了所有必要的编译器路径和变量。

  • 如何找到开发者命令提示符
    • 开始菜单 -> 搜索 "Developer Command Prompt for VS" 或 "x64 Native Tools Command Prompt"。
    • 或者,在普通命令提示符中,你可以运行 vcvarsall.bat 脚本来配置当前会话,对于 64 位 Python,运行 vcvarsall.bat x64

安装 setuptoolswheel

setuptools 是现代 Python 构建的标准工具。

pip install setuptools wheel

实战演练:创建一个简单的 C 扩展

我们将创建一个名为 my_math 的 C 扩展,它包含一个函数 add(a, b),用于计算两个整数的和。

步骤 1:创建项目文件结构

创建一个新文件夹,my_math_extension,并在其中创建以下文件:

my_math_extension/
├── setup.py
└── my_math.c

步骤 2:编写 C 代码 (my_math.c)

这是扩展的核心,我们将使用 Python C API 来创建一个函数,并将其暴露给 Python。

// my_math.c
// 1. 包含 Python.h
// 这个头文件定义了所有 Python C API 的函数和类型
#include <Python.h>
// 2. 定义 C 函数
// 这个函数的实际逻辑,接收两个 long 参数,返回它们的和
static long c_add(long a, long b) {
    return a + b;
}
// 3. 定义 Python 包装函数
// 这个函数是 Python 可以直接调用的接口
// 它接收一个 Python 参数对象 (PyObject *args),处理参数,并返回一个 Python 对象 (PyObject *)
static PyObject* py_add(PyObject* self, PyObject* args) {
    long a, b;
    // PyArg_ParseTuple 用于解析 Python 传入的参数
    // "ll" 表示期望两个 long 参数
    // &a, &b 是用于接收解析后值的变量指针
    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        // 如果解析失败,返回 NULL (Python 会将其视为异常)
        return NULL;
    }
    // 调用我们真正的 C 函数
    long result = c_add(a, b);
    // PyLong_FromLong 将 C 的 long 类型转换为 Python 的 int 对象
    return PyLong_FromLong(result);
}
// 4. 定义模块的方法表
// 这是一个静态数组,列出了模块中所有可用的函数及其对应的 C 函数指针
// { "Python函数名", C函数指针, 文档字符串, 参数数量 (可选) }
static PyMethodDef MyMathMethods[] = {
    {"add", py_add, METH_VARARGS, "Add two integers."},
    // 数组必须以 {NULL, NULL, 0, NULL} 
    {NULL, NULL, 0, NULL}
};
// 5. 定义模块定义结构体
// 这是模块的入口点,Python 解释器在加载模块时会查找它
// PyModuleDef_HEAD_INIT 是一个宏,用于初始化基部分
// my_math 是模块的名称
// -1 表示模块的版本号,通常为 -1 表示使用 API 版本
static struct PyModuleDef my_math_module = {
    PyModuleDef_HEAD_INIT,
    "my_math",
    NULL,
    -1,
    MyMathMethods
};
// 6. 模块初始化函数
// 当 Python 执行 `import my_math` 时,这个函数会被调用
// 函数名必须为 `PyInit_<模块名>`
PyMODINIT_FUNC PyInit_my_math(void) {
    // PyModule_Create 创建并初始化模块对象
    return PyModule_Create(&my_math_module);
}

步骤 3:编写构建脚本 (setup.py)

这个脚本告诉 setuptools 如何构建我们的扩展。

# setup.py
from setuptools import setup, Extension
# 定义我们的 C 扩展模块
# 第一个参数 "my_math" 是模块的名称
# sources 列出了所有 C 源文件
my_math_module = Extension(
    'my_math',
    sources=['my_math.c']
)
# setup() 函数配置了整个构建和打包过程
setup(
    name='my_math_extension',
    version='1.0',
    description='A simple C extension for math operations',
    ext_modules=[my_math_module]
)

步骤 4:编译和构建

打开 "开发者命令提示符",并导航到你的项目文件夹 (my_math_extension)。

运行以下命令:

# 构建一个 wheel 文件 (.whl)
# 这是推荐的现代方式,它会生成一个分发包
python setup.py bdist_wheel

你会看到编译器开始工作,输出类似下面的信息:

running bdist_wheel
running build
running build_ext
building 'my_math' extension
creating build
creating build\temp.win-amd64-cpython-311
creating build\temp.win-amd64-cpython-311\Release
"C:\Program Files\Microsoft Visual Studio\2025\Community\VC\Tools\MSVC\14.38.33130\bin\HostX64\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\Users\YourUser\AppData\Local\Programs\Python\Python311\include -IC:\Users\YourUser\AppData\Local\Programs\Python\Python311\include "-IC:\Program Files\Microsoft Visual Studio\2025\Community\VC\Auxiliary\VS\include" /Tcmy_math.c /Fo build\temp.win-amd64-cpython-311\Release\my_math.obj
... (更多编译信息) ...
...
building 'my_math' extension
creating build\lib.win-amd64-cpython-311
copying build\lib.win-amd64-cpython-311\my_math.pyd -> C:\path\to\your\project\my_math_extension\dist\my_math_extension-1.0-py3-none-win_amd64.whl\my_math.pyd
...

成功后,在 dist 文件夹下会生成一个 .whl 文件。

步骤 5:安装和测试

  1. 安装扩展: 使用 pip 来安装刚刚生成的 wheel 文件。pip 会自动找到并解压它,将 my_math.pyd 文件放到你的 Python site-packages 目录中。

    # 注意:替换下面的路径为你自己的 .whl 文件路径
    pip install dist\my_math_extension-1.0-py3-none-win_amd64.whl
  2. 测试扩展: 你可以在任何 Python 环境中(只要是从这个命令提示符对应的 Python 安装)使用这个扩展了。

    创建一个 test.py 文件:

    # test.py
    import my_math
    # 调用 C 扩展中的 add 函数
    result = my_math.add(10, 25)
    print(f"Result from C extension: {result}")
    # 检查类型
    print(f"Type of result: {type(result)}")

    运行 python test.py,你应该会看到:

    Result from C extension: 35
    Type of result: <class 'int'>

    恭喜!你已经成功地在 Windows 上创建并使用了一个 Python C 扩展。


常见问题与技巧

问题1:error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Build Tools for Visual Studio"

  • 原因:你正在尝试在一个没有配置好 MSVC 环境的普通命令提示符中运行 setup.py
  • 解决:确保你使用的是 "开发者命令提示符"

问题2:fatal error: Python.h: No such file or directory

  • 原因:编译器找不到 Python 的头文件,这可能是因为你没有安装 Python,或者 setup.py 没有正确找到 Python 的路径。
  • 解决
    1. 确认 Python 已正确安装并添加到 PATH。
    2. 在开发者命令提示符中运行 pythonpip 命令,确保它们指向正确的版本。
    3. setup.py 通常能自动找到 Python 的路径,但如果不行,你可以在 Extension 中手动指定 include_dirs
      my_math_module = Extension(
          'my_math',
          sources=['my_math.c'],
          include_dirs=[r'C:\path\to\your\python\include'] # 替换为你的Python安装路径
      )

技巧1:使用 build 库(现代替代方案)

setuptools 是传统的工具,现在有一个更现代、更简单的构建后端叫做 build,你可以用它来替代 python setup.py bdist_wheel

  1. 安装 build

    pip install build
  2. 在项目根目录下运行:

    python -m build

    这同样会生成 dist 文件夹,里面包含 wheel 和 sdist 源码包。

技巧2:调试 C 扩展

调试 C 扩展比调试普通 Python 代码要复杂,一个常用的技巧是使用 printf_CrtDbgReport (MSVC特有) 在关键位置打印信息,然后将输出重定向到一个文件中。

py_add 函数中添加调试信息:

static PyObject* py_add(PyObject* self, PyObject* args) {
    long a, b;
    printf("DEBUG: py_add called\n"); // 调试信息
    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        printf("DEBUG: PyArg_ParseTuple failed\n"); // 调试信息
        return NULL;
    }
    printf("DEBUG: Parsed args a=%ld, b=%ld\n", a, b); // 调试信息
    long result = c_add(a, b);
    printf("DEBUG: C function result=%ld\n", result); // 调试信息
    return PyLong_FromLong(result);
}

然后运行你的 Python 测试脚本,并将输出重定向:

python test.py > output.log 2>&1

检查 output.log 文件,看看你的调试信息是否打印出来了。

技巧3:使用 Visual Studio 进行调试

如果你想使用 Visual Studio 的图形化调试器来调试你的 C 代码:

  1. 在 Visual Studio 中创建一个新的 "C++ 空项目"。
  2. 将你的 my_math.c 文件添加到项目中。
  3. 右键项目 -> 属性 -> 配置属性 -> 调试。
  4. 将 "命令" 设置为你的 Python 解释器路径,C:\Users\YourUser\AppData\Local\Programs\Python\Python311\python.exe
  5. 将 "参数" 设置为你的测试脚本路径,C:\path\to\your\project\test.py
  6. my_math.c 中设置断点,然后点击 "开始调试"。

这样,当 Python 执行到你的 C 代码时,Visual Studio 就会中断并允许你单步调试。

分享:
扫描分享到社交APP
上一篇
下一篇