如何通过SWIG将Python函数赋值给C结构体的函数指针变量?
问题核心
需要将Python函数赋值给C结构体中的函数指针成员,但使用SWIG默认生成的setter函数时会触发类型不匹配错误,且无法修改原始C代码,只能通过SWIG/Python层面解决。
示例代码
C代码:
typedef void (*MyFunctionPtr)(); struct MyStruct { MyFunctionPtr func; };
Python代码:
import example def my_python_function(): print("Hello from Python!") my_struct = example.MyStruct() my_struct.func = my_python_function # 此处抛出类型不匹配错误
已尝试的无效方案
- 直接编写
void* MyFunctionPtr的typemap:因将PyObject指针直接强转为函数指针,违背内存语义,C代码调用时会执行无效内存,导致错误。 - 参考SWIG回调示例:未适配结构体成员赋值场景,未解决核心的函数指针与Python对象的映射问题。
- 全局PyObject存储方案:通过构造函数传递Python函数并绑定到全局变量,属于不良设计,无法支持多实例场景。
可行解决方案
核心思路是通过C包装函数中转:C函数指针指向一个符合签名的C包装函数,该包装函数负责调用对应的Python可调用对象,同时维护Python对象的引用计数避免被GC回收。
完整SWIG .i 配置
%module example %{ #include <Python.h> // 引入你的C头文件路径 #include "your_header.h" // 符合MyFunctionPtr签名的C包装函数,负责调用Python对象 static void py_callback_thunk() { PyObject* func = (PyObject*)SwigGetClientData(); if (func && PyCallable_Check(func)) { PyObject_CallObject(func, NULL); } } %} // 为MyStruct的func成员编写setter typemap,处理Python可调用对象 %typemap(set) MyFunctionPtr MyStruct::func %{ // 校验输入是否为可调用对象 if (!PyCallable_Check($input)) { PyErr_SetString(PyExc_TypeError, "必须传入可调用的Python对象"); return NULL; } // 清理旧的Python对象引用(如果存在) if ($self->func == py_callback_thunk) { PyObject* old_func = (PyObject*)SwigGetClientData(); Py_DECREF(old_func); } // 增加新对象的引用计数,防止被GC回收 Py_INCREF($input); // 将Python对象绑定到包装函数的客户端数据中 SwigSetClientData((void*)py_callback_thunk, $input); // 将结构体的函数指针指向包装函数 $1 = py_callback_thunk; %} // 为结构体增加析构函数,释放Python对象引用 %extend struct MyStruct { ~MyStruct() { if ($self->func == py_callback_thunk) { PyObject* func = (PyObject*)SwigGetClientData(); Py_DECREF(func); } } }; // 引入结构体定义 %include "your_header.h"
方案说明
- 包装函数中转:
py_callback_thunk是符合C函数指针签名的中间层,C代码调用结构体的func时,实际执行该包装函数,再由它调用对应的Python函数。 - 引用计数管理:通过
Py_INCREF和Py_DECREF维护Python对象的生命周期,避免被垃圾回收器误释放。 - 客户端数据绑定:利用SWIG的
SwigSetClientData和SwigGetClientData实现包装函数与Python对象的关联。 - 资源清理:通过扩展结构体的析构函数,在结构体销毁时释放Python对象的引用,避免内存泄漏。
Python端使用
配置完成后,原Python代码可以直接运行:
import example def my_python_function(): print("Hello from Python!") my_struct = example.MyStruct() my_struct.func = my_python_function # 赋值成功 # 当C代码调用my_struct->func()时,会自动触发Python函数执行
内容的提问来源于stack exchange,提问作者Maverickgugu
相关产品推荐
相关产品推荐

