事情是这样的我有一个c接口接收一个类似void(*CB)(Data)的函数指针当我尝试将此接口暴露给python的时候AI一步到位给我写好了转换代码如下#include cstdio #include pybind11/pybind11.h #include pybind11/stl.h #include pybind11/functional.h namespace py pybind11; typedef void(*CB)(int); class Test{ public: void test(CB cb) { printf(printf); } }; PYBIND11_MODULE(testpy, m) { m.doc() pybind11 wrapper for testpy class; py::class_Test(m, Test) .def(py::init(), Constructor for the Test class) .def(test, [](Test self, std::functionvoid(int) callback) { //... ... auto cb [callback](intd){ callback(d); }; // 下面这行报错错误信息类似 error: cannot convert ‘pybind11_init_testpy(pybind11::module_)::lambda(Test, std::functionvoid(int))::lambda(int)’ to ‘CB’ {aka ‘void (*)(int)’} return self.test(cb); } }import testpy def test_cb(i): print(ftest_cb i {i}) t testpy.Test() t.test()首先这里有个结论AI给出的代码是错误的(带捕获参数的lambda不能直接转换为裸指针可看注释部分)但是形式上面给出了转换代码的大致框架这里精简后整个问题的核心就是pybind11的std::function怎么转换为函数指针。下面我们看看怎么解决上面3个问题并解决最终问题。问题1python的函数怎么转换为pybind11中的std::function底层原理要解决这个问题我们得了解python的函数传递过来这里的callback到底是什么东西由于pybind11是大量的宏来生成代码为了快速得到我们要的内容我们使用gdb来对堆栈进行分析。#0 test_cb (i0x7fffffffd714: 9999) at testpy.cpp:19 #1 0x00007ffff73bfded in Test::test (this0xc49820, cb0x7ffff73ab120 test_cb(int)) at testpy.cpp:13 #2 0x00007ffff73ab3f7 in operator()(Test , std::functionvoid(int)) const (__closure0xc49678, self..., callback...) at testpy.cpp:33 #3 0x00007ffff73ac08c in pybind11::detail::argument_loaderTest, std::functionvoid(int) ::call_implvoid, pybind11_init_testpy(pybind11::module_)::lambda(Test, std::functionvoid(int)), 0, 1, pybind11::detail::void_type(struct {...} , std::index_sequence, pybind11::detail::void_type ) (this0x7fffffffd8a0, f...) at /usr/include/pybind11/cast.h:1480 #4 0x00007ffff73abd00 in pybind11::detail::argument_loaderTest, std::functionvoid(int) ::callvoid, pybind11::detail::void_type, pybind11_init_testpy(pybind11::module_)::lambda(Test, std::functionvoid(int))(struct {...} ) (this0x7fffffffd8a0, f...) at /usr/include/pybind11/cast.h:1454 #5 0x00007ffff73ab9b7 in operator() (__closure0x0, call...) at /usr/include/pybind11/pybind11.h:254 #6 0x00007ffff73aba6c in _FUN () at /usr/include/pybind11/pybind11.h:224 #7 0x00007ffff73bd52c in pybind11::cpp_function::dispatcher (self0x7ffff75fedc0, args_in0x7ffff7421f80, kwargs_in0x0) at /usr/include/pybind11/pybind11.h:946 #8 0x0000000000581ecf in cfunction_call (func0x7ffff740ba10, argsoptimized out, kwargsoptimized out) at ../Objects/methodobject.c:537 #9 0x0000000000549205 in _PyObject_MakeTpCall (tstate0xba5748 _PyRuntime459656, callable0x7ffff740ba10, argsoptimized out, nargs2, keywords0x0) at ../Objects/call.c:240 #10 0x0000000000549c3d in _PyObject_VectorcallTstate (kwnamesoptimized out, nargsfoptimized out, argsoptimized out, callableoptimized out, tstateoptimized out) at ../Include/internal/pycore_call.h:90 #11 0x00000000005d7109 in _PyEval_EvalFrameDefault (tstatetstateentry0xba5748 _PyRuntime459656, frameoptimized out, frameentry0x7ffff7fb2020, throwflagthrowflagentry0) at Python/bytecodes.c:2706 #12 0x00000000005d564b in _PyEval_EvalFrame (throwflag0, frame0x7ffff7fb2020, tstate0xba5748 _PyRuntime459656) at ../Include/internal/pycore_ceval.h:89 #13 _PyEval_Vector (kwnames0x0, argcount0, args0x0, locals0x7ffff75f9a80, func0x7ffff75da160, tstate0xba5748 _PyRuntime459656) at ../Python/ceval.c:1683 #14 PyEval_EvalCode (cocoentry0x7ffff75604b0, globalsglobalsentry0x7ffff75f9a80, localslocalsentry0x7ffff75f9a80) at ../Python/ceval.c:578 #15 0x00000000006087b2 in run_eval_code_obj (locals0x7ffff75f9a80, globals0x7ffff75f9a80, co0x7ffff75604b0, tstate0xba5748 _PyRuntime459656) at ../Python/pythonrun.c:1722 #16 run_mod (modoptimized out, filenameoptimized out, globals0x7ffff75f9a80, locals0x7ffff75f9a80, flagsoptimized out, arenaoptimized out) at ../Python/pythonrun.c:1743 #17 0x00000000006b4853 in pyrun_file (fpfpentry0xbf6480, filenamefilenameentry0x7ffff7409ca0, startstartentry257, globalsglobalsentry0x7ffff75f9a80, localslocalsentry0x7ffff75f9a80, closeitcloseitentry1, flags0x7fffffffe0a8) at ../Python/pythonrun.c:1643 #18 0x00000000006b45ba in _PyRun_SimpleFileObject (fpfpentry0xbf6480, filenamefilenameentry0x7ffff7409ca0, closeitcloseitentry1, flagsflagsentry0x7fffffffe0a8) at ../Python/pythonrun.c:433 #19 0x00000000006b43ef in _PyRun_AnyFileObject (fp0xbf6480, filenamefilenameentry0x7ffff7409ca0, closeitcloseitentry1, flagsflagsentry0x7fffffffe0a8) at ../Python/pythonrun.c:78 #20 0x00000000006bc455 in pymain_run_file_obj (skip_source_first_line0, filename0x7ffff7409ca0, program_name0x7ffff75f9bf0) at ../Modules/main.c:360 #21 pymain_run_file (config0xb48328 _PyRuntime77672) at ../Modules/main.c:379 #22 pymain_run_python (exitcode0x7fffffffe09c) at ../Modules/main.c:629 #23 Py_RunMain () at ../Modules/main.c:709 #24 0x00000000006bbf3d in Py_BytesMain (argcoptimized out, argvoptimized out) at ../Modules/main.c:763 #25 0x00007ffff7c2a1ca in __libc_start_call_main (mainmainentry0x518ac0 main, argcargcentry2, argvargventry0x7fffffffe2e8) at ../sysdeps/nptl/libc_start_call_main.h:58 #26 0x00007ffff7c2a28b in __libc_start_main_impl (main0x518ac0 main, argc2, argv0x7fffffffe2e8, initoptimized out, finioptimized out, rtld_finioptimized out, stack_end0x7fffffffe2d8) at ../csu/libc-start.c:360 #27 0x00000000006574f5 in _start ()从上面看核心就是从python解释器到了pybind11::cpp_function::dispatcher然后到了我们的test函数。如果我们对pybind11不熟悉的话我们还是需要深入去看pybind11才能回答我们上面的问题但是我这里想到了另外一个办法。我们都知道pybind11底层是由cpython实现的因此我们通过cpython来实现上面的同样的功能是什么样子的呢直接让AI生成示例如下#define PY_SSIZE_T_CLEAN #include Python.h #include cstdio #include functional // --- C 逻辑模拟部分 --- typedef void(*CB)(int); class Test{ public: void test(CB cb) { int i 9999; cb(i); } }; void test_cb(int i) { printf(test_cb from cxx i %d\n, i); } // --- CPython 包装部分 --- // 定义 Python 中的 Test 对象结构 typedef struct { PyObject_HEAD Test* cpp_obj; // 指向实际的 C 对象 } PyTestObject; void pybind11_like_func(Test self, std::functionvoid(int) callback) { //... ... auto cb [callback](intd){ callback(d); }; int i 8888; cb(i); // return self.test(cb); return self.test(test_cb); } // Test.test(callback) 的实现 static PyObject* PyTest_test(PyTestObject* self, PyObject* args) { PyObject* pycallback NULL; // 1. 解析参数期望得到一个可调用对象 if (!PyArg_ParseTuple(args, O, pycallback)) { return NULL; } if (!PyCallable_Check(pycallback)) { PyErr_SetString(PyExc_TypeError, Parameter must be callable); return NULL; } // 2. 核心模拟 std::functionvoid(int) 的构造 // 我们在这里捕获 py_callback 指针。注意实际生产中需要处理引用计数 std::functionvoid(int) cpp_callback [pycallback](int d) { // A. 必须获取 GIL因为回调可能由 C 触发 PyGILState_STATE gstate PyGILState_Ensure(); // B. 参数转换C int - Python Long PyObject* arg PyLong_FromLong((long)d); PyObject* arg_tuple PyTuple_Pack(1, arg); // C. 调用 Python 函数 PyObject* result PyObject_CallObject(pycallback, arg_tuple); // D. 错误处理与清理 if (!result) { PyErr_Print(); } Py_XDECREF(result); Py_DECREF(arg_tuple); Py_DECREF(arg); // E. 释放 GIL PyGILState_Release(gstate); }; pybind11_like_func(*self-cpp_obj, cpp_callback); Py_RETURN_NONE; } // --- 类型与模块定义 --- static PyMethodDef PyTest_methods[] { {test, (PyCFunction)PyTest_test, METH_VARARGS, Execute test with callback}, {NULL, NULL, 0, NULL} }; static PyTypeObject PyTestType { PyVarObject_HEAD_INIT(NULL, 0) .tp_name testcpy.Test, .tp_basicsize sizeof(PyTestObject), .tp_itemsize 0, .tp_flags Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_methods PyTest_methods, .tp_new PyType_GenericNew, }; static struct PyModuleDef testcpymodule { PyModuleDef_HEAD_INIT, testcpy, CPython version of testcpy, -1, NULL }; PyMODINIT_FUNC PyInit_testcpy(void) { PyObject* m; if (PyType_Ready(PyTestType) 0) return NULL; m PyModule_Create(testcpymodule); if (m NULL) return NULL; Py_INCREF(PyTestType); PyModule_AddObject(m, Test, (PyObject*)PyTestType); return m; }其实我们已经看到了我们用cpython来实现的话调用的std::function一定是一个带状态的callable obj。至此我们已经解决了问题1。问题2pybind11中的std::function 怎么转换为c层的裸函数实际的方法就是在pybind11代码层添加一个全局静态变量进行转换参考如下代码(重点查看PyCBWrapper相关的内容)#include cstdio #include pybind11/pybind11.h #include pybind11/stl.h #include pybind11/functional.h #include functional namespace py pybind11; typedef void(*CB)(int); class Test{ public: void test(CB cb) { int i 9999; cb(i); } }; struct PyCBWrapper { static std::functionvoid(int) py_cb; static void trampoline(int i) { if (nullptr ! py_cb) PyCBWrapper::py_cb(i); } }; std::functionvoid(int) PyCBWrapper::py_cb nullptr; void test_cb(int i) { printf(test_cb from cxx i %d\n, i); } PYBIND11_MODULE(testpy, m) { m.doc() pybind11 wrapper for testpy class; py::class_Test(m, Test) .def(py::init(), Constructor for the Test class) .def(test, [](Test self, std::functionvoid(int) callback) { //... ... auto cb [callback](intd){ callback(d); }; int i 8888; cb(i); PyCBWrapper::py_cb callback; // return self.test(cb); return self.test(PyCBWrapper::trampoline); }); }问题3pybind11中的std::function对象复制的注意事项问题2的这段代码会直接运行报错堆栈如下#0 __pthread_kill_implementation (no_tid0, signo6, threadidoptimized out) at ./nptl/pthread_kill.c:44 #1 __pthread_kill_internal (signo6, threadidoptimized out) at ./nptl/pthread_kill.c:78 #2 __GI___pthread_kill (threadidoptimized out, signosignoentry6) at ./nptl/pthread_kill.c:89 #3 0x00007ffff7c4527e in __GI_raise (sigsigentry6) at ../sysdeps/posix/raise.c:26 #4 0x00007ffff7c288ff in __GI_abort () at ./stdlib/abort.c:79 #5 0x00000000004b1252 in ?? () #6 0x00000000004b2908 in _Py_FatalErrorFunc () #7 0x00000000004b2cd2 in ?? () #8 0x000000000060861e in ?? () #9 0x00000000006a6e93 in PyEval_AcquireThread () #10 0x00007ffff73b9d20 in pybind11::gil_scoped_acquire::gil_scoped_acquire (this0x7fffffffd760) at /usr/include/pybind11/gil.h:82 #11 0x00007ffff73dd1b1 in pybind11::detail::type_casterstd::functionvoid (int), void::load(pybind11::handle, bool)::func_handle::~func_handle() (this0xc49870, __in_chrgoptimized out) at /usr/include/pybind11/functional.h:97 #12 0x00007ffff73dd324 in pybind11::detail::type_casterstd::functionvoid (int), void::load(pybind11::handle, bool)::func_wrapper::~func_wrapper() (this0xc49870, __in_chrgoptimized out) at /usr/include/pybind11/functional.h:103 #13 0x00007ffff73e0ff2 in std::_Function_base::_Base_managerpybind11::detail::type_casterstd::functionvoid(int), void::load(pybind11::handle, bool)::func_wrapper::_M_destroy (__victim...) at /usr/include/c/13/bits/std_function.h:175 --Type RET for more, q to quit, c to continue without paging-- #14 0x00007ffff73e0ce4 in std::_Function_base::_Base_managerpybind11::detail::type_casterstd::functionvoid(int), void::load(pybind11::handle, bool)::func_wrapper::_M_manager (__dest..., __source..., __opstd::__destroy_functor) at /usr/include/c/13/bits/std_function.h:203 #15 0x00007ffff73e02aa in std::_Function_handlervoid(int), pybind11::detail::type_casterstd::functionvoid(int), void::load(pybind11::handle, bool)::func_wrapper::_M_manager (__dest..., __source..., __opstd::__destroy_functor) at /usr/include/c/13/bits/std_function.h:282 #16 0x00007ffff73b62c1 in std::_Function_base::~_Function_base (this0x7ffff73ff440 PyCBWrapper::py_cb, __in_chrgoptimized out) at /usr/include/c/13/bits/std_function.h:244 #17 0x00007ffff73bff78 in std::functionvoid(int)::~function (this0x7ffff73ff440 PyCBWrapper::py_cb, __in_chrgoptimized out) at /usr/include/c/13/bits/std_function.h:334 #18 0x00007ffff7c47a76 in __run_exit_handlers (status0, listpoptimized out, run_list_atexitrun_list_atexitentrytrue, run_dtorsrun_dtorsentrytrue) at ./stdlib/exit.c:108 #19 0x00007ffff7c47bbe in __GI_exit (statusoptimized out) at ./stdlib/exit.c:138 #20 0x00007ffff7c2a1d1 in __libc_start_call_main (mainmainentry0x518c60, argcargcentry2, argvargventry0x7fffffffda28) at ../sysdeps/nptl/libc_start_call_main.h:74 #21 0x00007ffff7c2a28b in __libc_start_main_impl (main0x518c60, argc2, argv0x7fffffffda28, initoptimized out, finioptimized out, rtld_finioptimized out, stack_end0x7fffffffda18) at ../csu/libc-start.c:360 #22 0x0000000000657b05 in _start ()从堆栈分析可知问题出在程序退出的时候PyCBWrapper::py_cb全局静态变量析构的时候这个时候其实python解释器已经退出了再使用python解释器相关的资源就会报错。这个其实就是问题3。由于从上面的例子可以知道这个时候的PyCBWrapper::py_cb是一个捕获了python函数的PyObject的std::function那么解决方案也很简单那就是在上面的pybind11代码层中添加如下核心代码PYBIND11_MODULE(testpy, m) { m.doc() pybind11 wrapper for testpy class; py::class_Test(m, Test) .def(py::init(), Constructor for the Test class) .def(test, [](Test self, std::functionvoid(int) callback) { //... ... auto cb [callback](intd){ callback(d); }; int i 8888; cb(i); PyCBWrapper::py_cb callback; // return self.test(cb); self.test(PyCBWrapper::trampoline);