如何在Cython中将成员函数作为原生代码的回调函数
解决Cython中类成员函数作为C风格回调的问题
你遇到的核心问题是:C风格的普通函数指针和C++类成员函数(或std::bind生成的仿函数)类型不兼容。成员函数需要绑定到具体的对象实例才能调用,而原C++回调的定义没有预留传递对象上下文的位置,直接传成员函数或者用std::bind都会出现类型不匹配的问题。下面分两种场景给出解决方案:
推荐方案:修改C++回调定义(添加上下文参数)
这是最合理的长期解决方案,因为原C++回调的设计缺少传递用户数据的能力,修改后可以安全地支持对象成员函数回调,且线程安全。
1. 更新C++代码
修改sampleCallback.h,给回调添加void*类型的上下文参数,并在类中保存该参数:
namespace mango { class sampleCalls { public: // 回调新增user_data参数,用于传递对象指针 typedef void (*Callback)(char *name, void* user_data); // 构造函数新增user_data参数 sampleCalls(Callback method, char *name, void* user_data); void execute(); private: Callback _method; char *_key; void* _user_data; // 保存上下文数据 }; }
修改sampleCallback.cpp的实现,在调用回调时传递上下文:
namespace mango { sampleCalls::sampleCalls(Callback method, char *name, void* user_data) { _method = method; _key = name; _user_data = user_data; }; void sampleCalls::execute() { // 调用回调时传入保存的上下文 return _method(_key, _user_data); }; }
2. 编写Cython封装
在Cython中,我们需要一个静态中间函数作为C风格回调的入口,通过上下文参数拿到pyCallback对象,再调用成员函数:
from cpython cimport PyObject # 匹配修改后的回调类型 ctypedef void (*Callback)(char *name, void* user_data) cdef extern from "sampleCallback.h" namespace "mango": cdef cppclass sampleCalls: sampleCalls(Callback method, char *name, void* user_data) void execute() cdef class pyCallback: cdef sampleCalls* sc # 你的成员函数回调逻辑 cdef void cb(self, char* name): print name, " WINS" # 静态中间函数:作为C风格回调的入口 cdef static void static_callback(char* name, void* user_data): # 将void*转换为pyCallback对象 cdef pyCallback self = <pyCallback>user_data # 增加引用计数防止对象被提前销毁 PyObject* obj = <PyObject*>self Py_INCREF(obj) try: # 调用成员函数 self.cb(name) finally: Py_DECREF(obj) def __init__(self): # 将当前对象作为上下文参数传递给C++类 self.sc = new sampleCalls(static_callback, "BATMAN", <void*>self) def execute(self): self.sc.execute() def __dealloc__(self): # 释放C++对象内存 del self.sc
这样修改后,你的测试代码就能正常调用成员函数作为回调了,而且支持多实例、多线程场景。
备选方案:不修改C++代码(单线程场景可用)
如果无法修改原C++代码,只能用全局变量临时保存对象指针,但这种方法线程不安全,同一时间只能有一个pyCallback实例有效:
ctypedef void (*Callback)(char *name) cdef extern from "sampleCallback.h" namespace "mango": cdef cppclass sampleCalls: sampleCalls(Callback method, char *name) void execute() # 全局变量保存当前回调对象(仅单线程安全) cdef pyCallback* global_callback_obj = NULL cdef class pyCallback: cdef sampleCalls* sc cdef void cb(self, char* name): print name, " WINS" cdef static void static_callback(char* name): if global_callback_obj is not NULL: global_callback_obj[0].cb(name) def __init__(self): global global_callback_obj # 注意:多实例会覆盖全局变量,导致错误 global_callback_obj = &self self.sc = new sampleCalls(static_callback, "BATMAN") def execute(self): self.sc.execute() def __dealloc__(self): global global_callback_obj if global_callback_obj == &self: global_callback_obj = NULL del self.sc
为什么之前的std::bind会报错?
std::bind返回的是一个仿函数对象,而C风格回调要求的是普通函数指针,两者类型完全不兼容。Cython无法将仿函数转换为C语言的函数指针,因此会出现类型转换错误。这种场景下必须用静态函数作为中间层,不能直接用std::bind。
内容的提问来源于stack exchange,提问作者wolv3r1n3
相关产品推荐
相关产品推荐

