You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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的双向回调交互啦!

火山引擎 最新活动