如何在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
相关产品推荐
相关产品推荐

