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

如何在Tauri的Tokio异步运行时中从C FFI调用异步函数?

问题描述

我正在开发一个Tauri应用,用btleplug crate实现蓝牙调用,同时接入MetaWear传感器库。已经成功生成MetaWear库的Rust绑定,当前正在配置C库定义的结构体回调,以在btleplug中执行异步蓝牙操作。

核心问题:找不到正确的语法处理生命周期,也无法在Tauri的Tokio async_runtime中实现阻塞同步调用。

背景细节

board与btleplug的Peripheral对象在#[tauri::command(async)]代码块中初始化。MetaWear的board结构体允许通过context(类型为*mut ::std::os::raw::c_void)传入用户数据,我传入的是已完成蓝牙连接的装箱Peripheral实例。MetaWear板初始化过程中会调用传入结构体的回调函数,比如read_gatt_char。

最初尝试在unsafe extern "C" fn read_gatt_char中直接发起异步调用失败,转而通过unsafe fn on_read_gatt_char规避,但将该函数设为async后发现Rust的Future是惰性的,即使使用await也不会执行。后续尝试多种阻塞调用写法,要么出现生命周期不足的编译错误,要么触发运行时panic(提示无法启动新的Tokio运行时)。

当前编译错误

error[E0597]: `**cb` does not live long enough
   --> src\main.rs:364:18
    |
348 |     let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);
    |         -- binding `cb` declared here
...
364 |     let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                  |
    |                  borrowed value does not live long enough
    |                  argument requires that `**cb` is borrowed for `'static`
...
395 | }
    | - `**cb` dropped here while still borrowed

error[E0597]: `c` does not live long enough
   --> src\main.rs:364:73
    |
359 |     let c = Characteristic {
    |         - binding `c` declared here
...
364 |     let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
    |                  -------------------------------------------------------^^---
    |                  |                                                      |
    |                  |                                                      borrowed value does not live long enough
    |                  argument requires that `c` is borrowed for `'static`
...
395 | }
    | - `c` dropped here while still borrowed

相关代码片段

初始化代码

#[tauri::command(async)]
async fn bluetooth(state: tauri::State<'_, ConnectedDevices>) -> Result<Vec<String>, ()> {
    let manager = BManager::new().await.unwrap();
 
...
let peripheral: Box<Box<dyn PeripheralExt>> = Box::new(Box::new(p.clone()));

let btle_conn = MblMwBtleConnection {
    write_gatt_char: Some(write_gatt_char),
    read_gatt_char: Some(read_gatt_char),
    enable_notifications: Some(enable_notifications),
    on_disconnect: Some(on_disconnect),
    context: Box::into_raw(peripheral) as *mut c_void,
};
let board;
unsafe {
    board = mbl_mw_metawearboard_create(&btle_conn);

    // 每次连接板都必须初始化
    if mbl_mw_metawearboard_is_initialized(board) == 0 {
        mbl_mw_metawearboard_initialize(
            board,
            ptr::null_mut(),
            Some(status), // 调用status回调时板初始化失败
        );
    }

    let model =
        CStr::from_ptr(mbl_mw_metawearboard_get_model_name(board));
    trace!("model: {:?}", model);
}                               

生成的MblMwBtleConnection绑定结构体

#[doc = " Wrapper class containing functions for communicating with the MetaWear through a Bluetooth Low Energy connection."]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MblMwBtleConnection {
    #[doc = " Provides the calling function the ability to pass any context specific data required"]
    pub context: *mut ::std::os::raw::c_void,
    #[doc = " Reads the value of the characteristic from the device.\n @param context               Pointer to the <code>context</code> field\n @param caller                Object using this function pointer\n @param characteristic        Gatt characteristic to read\n @param handler               Callback function to handle the received value"]
    pub read_gatt_char: ::std::option::Option<
        unsafe extern "C" fn(
            context: *mut ::std::os::raw::c_void,
            caller: *const ::std::os::raw::c_void,
            characteristic: *const MblMwGattChar,
            handler: MblMwFnIntVoidPtrArray,
        ),
    >,
...

PeripheralExt trait及实现

#[async_trait]
pub trait PeripheralExt {
    unsafe fn on_read_gatt_char(&'static self, characteristic: ACharacteristic<'static>) -> Result<Vec<u8>, btleplug::Error>;
}


#[async_trait]
impl<'a> PeripheralExt for Peripheral {
    unsafe fn on_read_gatt_char(&'static self, characteristic: ACharacteristic<'static>) -> Result<Vec<u8>, btleplug::Error> {
        let ac = characteristic.characteristic;

        let output = tauri::async_runtime::block_on(async {
            let ac = characteristic.characteristic;
            let future = self.read(&ac);
            let task = tauri::async_runtime::spawn(future);
            task.await
        });

        let result = output.unwrap();
        trace!("after on_read_gatt_char {:?}", result);
        return result
    }
}

unsafe extern "C" fn read_gatt_char(
    context: *mut  c_void,
    caller: *const c_void,
    characteristic: *const MblMwGattChar,
    handler: MblMwFnIntVoidPtrArray,
) {
    trace!("Reading characteristic: {{service_uuid_high: {:x}, service_uuid_low: {:x}, uuid_high: {:x}, uuid_low: {:x}}}\n", (*characteristic).service_uuid_high, (*characteristic).service_uuid_low,
    (*characteristic).uuid_high, (*characteristic).uuid_low);

    if handler.is_none() {
        return;
    }
    let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);

    let c = Characteristic {
        uuid: uuid::Uuid::from_str(uuid_str.as_str()).unwrap(),
        service_uuid: uuid::Uuid::from_str(service_uuid.as_str()).unwrap(),
        properties: CharPropFlags::READ,
    };
    let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
    let val = output.unwrap();
    trace!("after on_read_gatt_char {:?}", val);
    handler.unwrap()(caller, val.as_ptr(),  (val.len() as i32).try_into().unwrap());

触发运行时panic的写法

let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);
    let future = cb.on_read_gatt_char(c);
    let task = tauri::async_runtime::spawn(future);
    let output = tauri::async_runtime::block_on(async { task.await});

    let val = output.unwrap();
    trace!("after on_read_gatt_char {:?}", val);
    handler.unwrap()(caller, val.as_ptr(), val.len() as i32);

需求

作为Rust新手,希望得到:

  • 当前实现思路的合理性检查
  • 上述生命周期错误的解决方案

内容的提问来源于stack exchange,提问作者couharrison

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 21:34:54