MicroPython中C++用户模块调用FFI注册的Python回调函数的双向交互问题
MicroPython中C++用户模块调用FFI注册的Python回调函数的双向交互问题
我来帮你搞定这个双向交互的问题!你现在的核心问题是没摸透MicroPython里对象的调用逻辑——直接把mp_obj_t当C函数指针调用肯定行不通,咱们一步步拆解解决:
先理清楚你的现有代码
Python 端代码(已格式化)
import ffi import exemplecpp def hello(): print("Hello world\n") # 注册无参数无返回值的回调函数 hello_callback = ffi.callback("v", hello, "") exemplecpp.cppfunc(hello_callback)
C++/MicroPython 用户模块代码(问题点)
mp_obj_t cppfunc(mp_obj_t func_obj){ ... func_obj(); // 这里直接调用是错误的! ... return mp_const_none; }
问题根源
在MicroPython的生态里,所有传递给C模块的对象都是mp_obj_t类型的封装体——不管是普通Python函数还是通过FFI注册的回调,都不能直接当作C函数指针调用,必须用MicroPython官方提供的运行时API来触发执行。
正确实现步骤
1. 修改C模块的cppfunc函数
利用MicroPython的mp_call_function_*系列API来调用传入的可调用对象,针对你无参数的回调,用mp_call_function_0就刚好:
#include "py/obj.h" #include "py/runtime.h" mp_obj_t cppfunc(mp_obj_t func_obj) { // 先做个类型检查,避免传入非可调用对象导致崩溃,很有必要 if (!mp_obj_is_callable(func_obj)) { mp_raise_TypeError(MP_ERROR_TEXT("参数必须是可调用对象哦")); } // 调用无参数的回调函数 // 如果需要处理回调的返回值,可以写成:mp_obj_t result = mp_call_function_0(func_obj); mp_call_function_0(func_obj); // 要是你的回调需要传参数,比如传一个整数+字符串,参考这个写法: // mp_obj_t args[] = { // mp_obj_new_int(42), // mp_obj_new_str("test", strlen("test")) // }; // mp_call_function_n(func_obj, 2, args); return mp_const_none; } // 别忘了把函数注册到模块里(这是MicroPython用户模块的标准操作) STATIC MP_DEFINE_CONST_FUN_OBJ_1(cppfunc_obj, cppfunc); STATIC const mp_rom_map_elem_t exemplecpp_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_exemplecpp) }, { MP_ROM_QSTR(MP_QSTR_cppfunc), MP_ROM_PTR(&cppfunc_obj) }, }; STATIC MP_DEFINE_CONST_DICT(exemplecpp_module_globals, exemplecpp_module_globals_table); const mp_obj_module_t exemplecpp_module = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&exemplecpp_module_globals, }; MP_REGISTER_MODULE(MP_QSTR_exemplecpp, exemplecpp_module, MODULE_EXEMPLECPP_ENABLED);
2. 异步调用的额外注意事项
如果你想在C模块的线程、中断或者异步任务里触发这个回调,必须先获取MicroPython的GIL(全局解释器锁),否则会因为GC回收或线程冲突导致崩溃:
#include "py/mpstate.h" // 引入线程相关API void async_trigger_callback(mp_obj_t func_obj) { // 先获取GIL,确保线程安全 mp_thread_gil_enter(); // 安全调用回调 if (mp_obj_is_callable(func_obj)) { mp_call_function_0(func_obj); } // 释放GIL mp_thread_gil_exit(); }
3. Python端的小细节提醒
- 一定要保持对
hello_callback的全局引用!别把它写成局部变量——如果回调对象被Python的GC回收了,C端调用时直接会炸。你现在赋值给全局变量的做法完全正确。 ffi.callback的第三个参数可以给个可读性强的名字,比如ffi.callback("v", hello, "hello_callback"),后续调试会方便很多。
核心总结
说白了就是:MicroPython里所有对象都是mp_obj_t封装的黑盒,调用它必须用官方的mp_call_function_*API,不能直接当C函数指针用。另外只要注意两个点:回调对象别被GC回收、异步调用要拿GIL,就能顺利实现C模块和Python的双向回调交互啦!




