如何通过CFFI将自定义Python对象传入C暴露的函数?
用CFFI为接收void*的C函数编写通用Python绑定
我需要为某C库创建Python绑定,选用了CFFI工具。该库中有数十个函数,部分函数以void*作为输入,允许传入任意类型的数据,只需指定单个对象的大小和数量,即可接收任意数据数组。
核心问题:如何编写一个Python包装/绑定函数,使其能够接收任意输入,通过CFFI转换/映射到C端,并调用对应的C函数?
现有可行示例(针对已知类型)
目前我已成功调用接收预定义数据类型的函数,比如整数数组:
from _myPythonBindingLibModule import ffi, lib # 整数数组 data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] data_c = ffi.new("int[]", data) result = lib.doSomethingInC(data_c, len(data), ffi.sizeof(data_c[0]))
但问题在于,上述实现需要明确知道传入C函数的数据类型。而我需要一个能接收任意输入的通用包装函数,当前限制是CFFI的ffi.new()和ffi.from_buffer()仅支持POD类型(int、float、char等)数组或bytes类型。
思路分析与建议
1. 使用ffi.from_buffer()转换为bytes
该方案可行,虽需针对不同类型编写适配代码,但属于轻量级纯Python实现。
- 针对numpy数组:直接利用numpy的连续内存特性,通过
ffi.from_buffer()获取指针:import numpy as np def wrap_do_something(data): if isinstance(data, np.ndarray): if not data.flags.c_contiguous: data = data.copy(order='C') c_ptr = ffi.from_buffer(data) elem_size = data.dtype.itemsize elem_count = data.size return lib.doSomethingInC(c_ptr, elem_count, elem_size) - 针对Python内置POD类型列表:借助
array模块将列表转为连续内存数组,再适配给CFFI:import array def wrap_do_something(data): if isinstance(data, list): if not data: raise ValueError("空列表无法处理") elem_type = type(data[0]) # 可扩展支持更多POD类型 type_map = { int: 'i', float: 'd', bool: 'b' } if elem_type not in type_map: raise TypeError(f"不支持的列表元素类型:{elem_type}") arr = array.array(type_map[elem_type], data) c_ptr = ffi.from_buffer(arr) elem_size = arr.itemsize elem_count = len(arr) return lib.doSomethingInC(c_ptr, elem_count, elem_size)
缺点:无法直接处理自定义Python类对象,必须先将其序列化为连续内存的字节流。
2. 使用Pickle序列化
不推荐此方案。Pickle会将对象序列化为非结构化字节流,C端无法直接解析为原数据结构,且会额外分配内存,不符合“直接使用内存中数据”的需求。
3. 编写接收PyObject的C侧包装函数
这是最灵活的方案,尤其适合处理自定义Python对象或追求高性能的场景。
实现步骤:
- 在C侧编写包装函数,接收
PyObject*作为输入参数; - 通过Python C API将PyObject转换为连续内存缓冲区(如numpy数组直接取内部指针,列表则逐个提取元素复制到C数组);
- 调用原C库函数,传入转换后的
void*、元素数量和大小; - 清理临时分配的内存(如列表转换时的C数组),返回结果给Python层。
示例C包装代码:
#include <Python.h> #include "your_c_library.h" // 若需处理numpy数组,需包含头文件并初始化API #include <numpy/arrayobject.h> static PyObject* wrap_doSomethingInC(PyObject* self, PyObject* args) { PyObject* data_obj; if (!PyArg_ParseTuple(args, "O", &data_obj)) { return NULL; } void* c_ptr = NULL; Py_ssize_t elem_count = 0; size_t elem_size = 0; int need_free = 0; // 处理numpy数组 if (PyArray_Check(data_obj)) { PyArrayObject* arr = (PyArrayObject*)data_obj; if (!PyArray_IS_C_CONTIGUOUS(arr)) { PyErr_SetString(PyExc_TypeError, "仅支持C连续的numpy数组"); return NULL; } c_ptr = PyArray_DATA(arr); elem_count = PyArray_SIZE(arr); elem_size = PyArray_ITEMSIZE(arr); } // 处理Python整数列表 else if (PyList_Check(data_obj)) { elem_count = PyList_Size(data_obj); elem_size = sizeof(int); c_ptr = malloc(elem_count * elem_size); if (!c_ptr) { PyErr_SetString(PyExc_MemoryError, "内存分配失败"); return NULL; } need_free = 1; for (Py_ssize_t i = 0; i < elem_count; i++) { PyObject* item = PyList_GetItem(data_obj, i); if (!PyLong_Check(item)) { free(c_ptr); PyErr_SetString(PyExc_TypeError, "列表元素必须是整数"); return NULL; } ((int*)c_ptr)[i] = (int)PyLong_AsLong(item); } } // 可扩展支持其他类型(如float列表、bytes等) else { PyErr_SetString(PyExc_TypeError, "不支持的数据类型"); return NULL; } // 调用原C函数 int result = doSomethingInC(c_ptr, elem_count, elem_size); // 释放临时分配的内存 if (need_free) { free(c_ptr); } return PyLong_FromLong(result); } // 注册模块方法 static PyMethodDef BindingMethods[] = { {"do_something_in_c", wrap_doSomethingInC, METH_VARARGS, "调用C库的doSomethingInC函数"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef binding_module = { PyModuleDef_HEAD_INIT, "_myPythonBindingLibModule", NULL, -1, BindingMethods }; PyMODINIT_FUNC PyInit__myPythonBindingLibModule(void) { import_array(); // 初始化numpy API return PyModule_Create(&binding_module); }
优势:可直接处理更多Python对象类型,避免Python层字节转换的开销,性能更优;缺点:需要编写C代码,增加了维护成本。
内容的提问来源于stack exchange,提问作者tfromont
相关产品推荐
相关产品推荐

