Table of Contents:

python API

为了支持扩展,Python API(应用程序编程接口)定义了一系列函数、宏和变量,可以访问 Python 运行时系统的大部分内容。Python 的 API 可以通过在一个 C 源文件中引用 "Python.h" 头文件来使用。

[!note]

C扩展接口特指CPython,扩展模块无法在其他Python实现上工作。在大多数情况下,应该避免写C扩展,来保持可移植性。举个例子,如果你的用例调用了C库或系统调用,你应该考虑使用 ctypes 模块或 cffi 库,而不是自己写C代码。这些模块允许你写Python代码来接口C代码,而且可移植性更好。

#include <Python.h>

static PyObject* sum(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return PyLong_FromLong(a + b);
}

static PyMethodDef methods[] = {
    {"sum", sum, METH_VARARGS, "Add two integers."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "mymodule",
    NULL,
    -1,
    methods
};

PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&module);
}

编译

g++ -o mymodule.so -shared -fPIC sum.cpp -I/usr/include/python3.8 -lpython3.8

python中调用

import mymodule

result = mymodule.sum(3, 5)
print("Sum:", result)

参考链接

pybind11

pybind11 — Seamless operability between C++11 and Python
本质上是对Python API的易用化的封装。

pybind的cmake的官方的简单的例子:cmake_example

#include <pybind11/pybind11.h>

#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)

int add(int i, int j) {
    return i + j;
}

namespace py = pybind11;

PYBIND11_MODULE(cmake_example, m) {
    m.doc() = R"pbdoc(
        Pybind11 example plugin
        -----------------------

        .. currentmodule:: cmake_example

        .. autosummary::
           :toctree: _generate

           add
           subtract
    )pbdoc";

    m.def("add", &add, R"pbdoc(
        Add two numbers

        Some other explanation about the add function.
    )pbdoc");

    m.def("subtract", [](int i, int j) { return i - j; }, R"pbdoc(
        Subtract two numbers

        Some other explanation about the subtract function.
    )pbdoc");

#ifdef VERSION_INFO
    m.attr("__version__") = "dev";
#else
    m.attr("__version__") = "dev";
#endif
}

windows下经过编译后会生成:cmake_example.cp311-win_amd64.pyd, linux下回生成.so文件, 把这个文件放到引用这个模块的python文件的目录中或python三方包的路径中,便可使用这个模块

import cmake_example as m

def test_main():
    assert m.__version__ == "0.0.1"
    assert m.add(1, 2) == 3
    assert m.subtract(1, 2) == -1

ctypes

ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。

//hello_module.c
#include <stdio.h>

int hello(const char* name) {
    printf("hello %s!\n", name);
    return 0;
}

int sum(int a[], size_t len) {
    int ret = 0;
    for (size_t i = 0; i < len; i++) {
        ret += a[i];
    }
    return ret;
}
import ctypes

# hello
lib = ctypes.cdll.LoadLibrary("hello_module.so")
lib.hello("world")  # hello world!

# sum
lib = ctypes.cdll.LoadLibrary("sum_module.so")
array = (ctypes.c_int * 3)(1, 2, 3)
print lib.sum(array, len(array))

i = ctypes.c_int(5)
print lib.sum(i, 1)

优点
原生支持,不需要依赖其他函式库
被呼叫的库不需要重新编译
缺点
对 C++ 的支持度不好
存取与变更 C 结构麻烦又容易出错
无法反方向从 C 程序呼叫 python

参考链接

CFFI

CFFI(C Foreign Function Interface)是一个 Python 库,用于调用和提供 C 语言函数接口。它允许 Python 代码直接调用本地 C 代码中的函数,或者让 C 代码调用 Python 中的函数。CFFI 是 Python 的外部函数接口(FFI)的一种实现,旨在提供简单、跨平台的方法来与 C 语言进行交互。

CFFI 通常用于以下几个方面:

  1. 调用本地 C 代码: 使用 CFFI,Python 可以调用本地编译的 C 函数,这些函数可能是为了性能或者与底层系统进行交互而编写的。

  2. 提供 C 函数接口: CFFI 允许 Python 将自己的函数暴露给 C 代码,使得 C 代码可以直接调用 Python 函数。

  3. 跨平台性: CFFI 在不同的操作系统上都提供一致的接口,因此可以轻松地编写跨平台的 Python 代码,与本地 C 代码进行交互。

CFFI 支持两种主要的调用模式:ABI(Application Binary Interface)和 API(Application Programming Interface)。ABI 模式允许 Python 直接调用 C 函数,而 API 模式则允许 Python 使用 C 函数指针进行间接调用。这使得 CFFI 非常灵活,可以满足不同场景下的需求。

下面的代码是soundcard库中的,pulseaudio.py中加载pulse声卡驱动的代码

_ffi = cffi.FFI()
_package_dir, _ = os.path.split(__file__)
with open(os.path.join(_package_dir, 'pulseaudio.py.h'), 'rt') as f:
    _ffi.cdef(f.read())   # 相当于把头文件的定义都载入了进去
try:
    _pa = _ffi.dlopen('pulse')
except OSError:
    # Try explicit file name, if the general does not work (e.g. on nixos)
    _pa = _ffi.dlopen('libpulse.so')
import cffi
# 创建一个 cffi.FFI() 对象
ffi = cffi.FFI()

# 定义动态库的接口
ffi.cdef("""
    int sum(int a, int b);
""")

# 加载动态库
lib = ffi.dlopen('./libsum.so')

# 调用动态库中的 sum 函数
result = lib.sum(3, 5)
print("Sum:", result)

优点
支持 pypy
可以把 c library 包装成 python module 来维护
缺点
不支持 C++
无法反方向从 C 程序呼叫 python