如何让PyBindGen自动设置全局PyObject* my_callback适配无上下文指针的回调API
解决PyBindGen无上下文指针的C回调绑定问题
我懂你的处境——PyBindGen官方文档标注回调是缺失功能,但你找到了用全局PyObject保存回调的思路,可惜没法让PyBindGen自动生成维护这个全局变量的关键代码。其实只要在你自定义的CallbackTypeParam类里加几行代码就能搞定,具体方案如下:
核心修改:在参数转换逻辑中维护全局回调引用
你需要在CallbackTypeParam.convert_python_to_c方法里,通过wrapper.before_call.write_code()把那两行关键代码插入到生成的绑定代码中。注意变量对应关系:py_cb是当前传入的Python回调对象,全局变量是my_callback。修改后的代码如下:
class CallbackTypeParam(Parameter): DIRECTIONS = [Parameter.DIRECTION_IN] CTYPES = ['CallbackType'] def convert_python_to_c(self, wrapper): assert isinstance(wrapper, ForwardWrapperBase) py_cb = wrapper.declarations.declare_variable("PyObject*", self.name) wrapper.parse_params.add_parameter('O', ['&'+py_cb], self.name) wrapper.before_call.write_error_check("!PyCallable_Check(%s)" % py_cb, """PyErr_SetString(PyExc_TypeError, "CallbackType parameter must be callable");""") # 新增:维护全局回调引用,释放旧引用+保存新引用 wrapper.before_call.write_code("Py_XDECREF(my_callback);") wrapper.before_call.write_code("my_callback = %s;" % py_cb) # 保留INCREF,确保全局变量持有有效引用(避免Python侧提前回收) wrapper.before_call.write_code("Py_INCREF(%s);" % py_cb) wrapper.call_params.append("_wrap_callback") # 移除自动清理的DECREF:我们需要全局长期持有引用,不能在这里释放 # wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % py_cb) def convert_c_to_python(self, wrapper): raise NotImplementedError
关键细节说明:
Py_XDECREF(my_callback):先释放之前保存的回调引用,避免用户多次调用register_cb时产生内存泄漏。my_callback = py_cb:把当前传入的Python回调对象赋值给全局变量,让_wrap_callback能找到它。- 移除自动清理的
Py_DECREF:原来的清理代码会在register_cb调用结束后释放py_cb,但我们需要全局变量长期持有这个引用,否则回调对象会被提前回收,导致后续set_num调用崩溃。
额外优化:模块销毁时清理全局引用
为彻底避免内存泄漏,建议在Python模块卸载时释放全局的my_callback引用。在my_module_gen函数中添加模块析构函数:
mod.body.writeln(""" static void cleanup_module(void) { Py_XDECREF(my_callback); my_callback = NULL; } """) mod.add_module_cleanup_function("cleanup_module")
完整修改后的modulegen.py
import sys import pybindgen from pybindgen import ReturnValue, Parameter, Module, Function, FileCodeSink from pybindgen import CppMethod, CppConstructor, CppClass, Enum from pybindgen.typehandlers.base import ForwardWrapperBase class CallbackTypeParam(Parameter): DIRECTIONS = [Parameter.DIRECTION_IN] CTYPES = ['CallbackType'] def convert_python_to_c(self, wrapper): assert isinstance(wrapper, ForwardWrapperBase) py_cb = wrapper.declarations.declare_variable("PyObject*", self.name) wrapper.parse_params.add_parameter('O', ['&'+py_cb], self.name) wrapper.before_call.write_error_check("!PyCallable_Check(%s)" % py_cb, """PyErr_SetString(PyExc_TypeError, "CallbackType parameter must be callable");""") # 新增:维护全局回调引用 wrapper.before_call.write_code("Py_XDECREF(my_callback);") wrapper.before_call.write_code("my_callback = %s;" % py_cb) # 保留INCREF,确保全局变量持有有效引用 wrapper.before_call.write_code("Py_INCREF(%s);" % py_cb) wrapper.call_params.append("_wrap_callback") # 移除自动清理的DECREF,避免回调被提前回收 # wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % py_cb) def convert_c_to_python(self, wrapper): raise NotImplementedError def my_module_gen(out_file): mod = Module('c') mod.add_include('"c.h"') mod.header.writeln(""" void _wrap_callback(int value); static PyObject *my_callback = NULL; """) mod.body.writeln(""" void _wrap_callback(int value) { int arg; PyObject *arglist; arg = value; printf("@@@@ Inside the binding: %d %p\n", value, my_callback); fflush(NULL); arglist = Py_BuildValue("(i)", arg); PyObject_CallObject(my_callback, arglist); Py_DECREF(arglist); } """) # 新增模块清理函数 mod.body.writeln(""" static void cleanup_module(void) { Py_XDECREF(my_callback); my_callback = NULL; } """) mod.add_module_cleanup_function("cleanup_module") # 替换默认参数处理,使用自定义的CallbackTypeParam mod.add_function("register_cb", None, [CallbackTypeParam("cb")]) mod.add_function("set_num", None, [Parameter.new("int", "n")]) mod.generate(FileCodeSink(out_file)) if __name__ == '__main__': my_module_gen(sys.stdout)
必做提醒:
一定要把原来的mod.add_function("register_cb", None, [Parameter.new("CallbackType", "cb")])改成使用自定义的CallbackTypeParam("cb"),否则自定义的参数转换逻辑不会生效!
测试验证
修改完成后,重新运行setup.py build,执行你的测试脚本:
import c def my_callback(value): print("In Callback: " + str(value)) c.register_cb(my_callback) c.set_num(10); c.set_num(20);
正常情况下会输出:
@@@@ Inside the binding: 10 0x... In Callback: 10 @@@@ Inside the binding: 20 0x... In Callback: 20
不会出现回调被提前回收导致的崩溃或内存泄漏问题。
内容的提问来源于stack exchange,提问作者Paul Grinberg
相关产品推荐
相关产品推荐

