You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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对象或追求高性能的场景。

实现步骤:

  1. 在C侧编写包装函数,接收PyObject*作为输入参数;
  2. 通过Python C API将PyObject转换为连续内存缓冲区(如numpy数组直接取内部指针,列表则逐个提取元素复制到C数组);
  3. 调用原C库函数,传入转换后的void*、元素数量和大小;
  4. 清理临时分配的内存(如列表转换时的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:14:51