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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:59:18