Python的C/C++绑定

1. Python/C API接口


Python/C API 是 Python 官方提供的 C 语言接口,允许 C/C++ 程序与 Python 解释器交互,CPython就是这套接口的实现。
这套接口有两种用法:

  1. 在Python代码中调用使用C/C++编写扩展模块:  把性能关键或系统级代码写成 C/C++ 函数,编译成共享库(.so / .pyd),在 Python 里像普通模块一样 import。
  2. 在C/C++代码中调用Python代码:  在 C 代码里启动 Python 虚拟机、执行 Python 脚本、调用 Python 函数、使用 Python 对象,从而直接复用现成的 Python 生态。

以CPython为例记录一下这两种用法。

2. 使用 C/C++ 编写 Python 模块

将C/C++与Python集成有两种主要开发模式:

  • 将C/C++函数公开为Python模块函数。
  • 从C++类创建新的Python类。

这里展示使用CPython将C函数公开为Python模块函数:

1. 编写C函数
mymodule.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <Python.h>

// 定义函数
static PyObject* add(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL;
    return PyLong_FromLong(a + b);
}


//2. 定义方法表 
static PyMethodDef module_methods[] = {
    {"add", add, METH_VARARGS, "Add two integers"},
    {NULL, NULL, 0, NULL}
};


//3. 定义模块结构
static struct PyModuleDef mymodule = {
    PyModuleDef_HEAD_INIT,
    "mymodule",
    "Example module that adds two numbers",
    -1,
    module_methods
};


// 4. 模块初始化
PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&mymodule);
}

2. 构建脚本
setup.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from setuptools import setup, Extension

ext = Extension(
    'mymodule',
    sources=['mymodule.c']
)

setup(
    name='mymodule',
    version='1.0',
    description='CPython extension example',
    ext_modules=[ext]
)

3. 编译

1
2
3
4
5
6
7
8
zci@Gem:~/000$ python3 setup.py build_ext --inplace
zci@Gem:~/000$
zci@Gem:~/000$ tree -L 1
.
├── build
├── mymodule.c
├── mymodule.cpython-312-x86_64-linux-gnu.so   # 编译成的动态库
└── setup.py

4. 使用模块
test.py

1
2
3
import mymodule
result = mymodule.add(3, 5)
print(result)  # 输出: 8

3. 在C/C++代码中调用Python代码

分为三步:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 1. 初始化Python解释器
void Py_Initialize();   
int Py_IsInitialized();  // 解析器的是否已经初始化完成,完成返回大于0,否则返回0。


// 2. 执行Python代码


// 3. 释放解释器资源
void Py_Finalize();     

3.1. 导入模块中的函数

接下来细说第2步: 从Python脚本文件中导入模块,并执行模块中的函数:
1. 添加 Python 脚本路径到 sys.path。

1
PyRun_SimpleString("import sys\nsys.path.append('path/to/_script')");

2. 导入 Python 模块:  使用 PyImport_ImportModule 导入模块。

1
2
3
4
5
PyObject* pModule = PyImport_ImportModule("mymodule"); // mymodule.py
if (!pModule) {
    PyErr_Print(); // 打印错误信息
    return -1;     // 处理错误
}

3. 获取模块中的函数对象:  通过 PyObject_GetAttrString 获取模块中的函数对象,PyObject_GetAttrString() 会增加返回对象的引用计数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 获取模块中的my_function函数。  
PyObject* pFunc = PyObject_GetAttrString(pModule, "my_function");  

// 检查 pFunc 是否为可调用对象(函数、方法、类等)
if (!pFunc || !PyCallable_Check(pFunc)) {
    PyErr_Print();
    Py_XDECREF(pFunc);   // 释放 pFunc 的引用计数
    Py_DECREF(pModule);  // 释放模块对象 pModule 的引用计数
    return -1;
}

4. 准备参数并调用函数: 根据函数的参数类型,使用 Py_BuildValue 构造参数,并通过 PyObject_CallObject 调用函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 例如 my_function 函数接受一个整数参数
// "(i)": 元组: (i,)。  
PyObject* pArgs = Py_BuildValue("(i)", 42);           // 构建 Python 参数对象,参数为 42
PyObject* pResult = PyObject_CallObject(pFunc, pArgs);


if (!pResult) {
    PyErr_Print();
} else {
    // 处理返回值,例如返回值是一个整数
    if (PyLong_Check(pResult))               // 检查一下给定的 pResult 对象是否是 PyLongObject(Python 整数对象)或其子类型
    {
       int result = PyLong_AsLong(pResult);  // 将 Python 的整数对象(PyLongObject)转换为 C/C++ 的 long 类型。 
       std::cout << "Python function returned: " << result << std::endl;
    }

}


// Py_BuildValue、PyObject_CallObject 都会增加返回对象的引用计数。
Py_XDECREF(pResult);
Py_DECREF(pArgs);

5. 释放资源

1
2
Py_XDECREF(pFunc);
Py_DECREF(pModule);

3.2. 导入模块中的类

myclass.py 如下:

1
2
3
4
5
6
7
8
class MyClass:
    def __init__(self, value):
        self.value = value

    def get_value(self):
        return self.value

    # 其他 ...

导入类型用的还是PyObject_GetAttrString(), 创建实例用的是也是PyObject_CallObject()。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <Python.h>
#include <iostream>

int main() {
    Py_Initialize();
    PyRun_SimpleString("import sys\nsys.path.append('./')");
    PyObject* pModule = PyImport_ImportModule("myclass");
    if (!pModule) {
        PyErr_Print();
        return -1;
    }

    // 1. 获取类对象, 这个 pClass 是类对象本身(type 对象),不是类的实例
    PyObject* pClass = PyObject_GetAttrString(pModule, "MyClass");
    if (!pClass || !PyType_Check(pClass)) {
        PyErr_Print();
        std::cerr << "MyClass not found or is not a class." << std::endl;
        Py_XDECREF(pClass);
        Py_DECREF(pModule);
        Py_Finalize();
        return -1;
    }

    // 2. 创建类实例(调用构造函数)
    PyObject* pArgs = Py_BuildValue("(i)", 100);              // 构造函数参数:(100,)
    PyObject* pInstance = PyObject_CallObject(pClass, pArgs); // 创建实例
    if (!pInstance) {
        PyErr_Print();
        std::cerr << "Failed to create instance of MyClass." << std::endl;
        Py_DECREF(pArgs);
        Py_DECREF(pClass);
        Py_DECREF(pModule);
        Py_Finalize();
        return -1;
    }

    // 3. 调用实例方法
    PyObject* pGetValueFunc = PyObject_GetAttrString(pInstance, "get_value");      // 获取 get_value() 方法
    if (pGetValueFunc && PyCallable_Check(pGetValueFunc)) {
        // 调用函数
        PyObject* pResult = PyObject_CallObject(pGetValueFunc, nullptr);
        if (pResult) {
            if (PyLong_Check(pResult)) {
                long value = PyLong_AsLong(pResult); // 获取函数返回
                std::cout << "get_value() returned: " << value << std::endl;
            }
            Py_DECREF(pResult);
        } else {
            PyErr_Print();
        }
        Py_DECREF(pGetValueFunc);
    }


    // 4. 清理资源 
    Py_DECREF(pArgs);
    Py_DECREF(pInstance);
    Py_DECREF(pClass);
    Py_DECREF(pModule);


    Py_Finalize();
    return 0;
}
comments powered by Disqus