How to make an async fn call from C FFI in Rust within tauri's tokio async_runtime

I am creating a Tauri app that uses the btleplug crate to make bluetooth calls. I also need to use a library for the sensors called MetaWear. I have been able to generate bindings for the MetaWear library and am now setting up callbacks from a struct defined by the C library to make async bluetooth calls in btleplug.

The issue is that I haven’t come up with exactly the right syntax with either lifetimes or for blocking sync calls within tauri’s tokio async_runtime.

The board and the btleplug Peripheral are initialized from within a #[tauri::command(async)] block. The board struct from the MetaWear library allows me to pass in user data via the context as *mut ::std::os::raw::c_void. I’m passing in a Boxed Peripheral that has been connected to bluetooth. As the MetaWear board is being initialized, it calls the functions passed into the struct like read_gatt_char.

The issue begins with being unable to make async calls from within theunsafe extern "C" fn read_gatt_char. I moved past this by trampolining out of the C FFI callback to unsafe fn on_read_gatt_char. Initially I had this function as async but learned fairly quickly that futures are lazy in Rust and so even though I had an await in the rusty async on_read_gatt_char, the promise was never being called. Then I tried a few different versions of adding blocking in this call. Errors ranged from lifetimes not being defined long enough to being unable to spawn another tokio runtime (this was a panic during runtime).

Since I’m new to Rust, I’d really appreciate a sanity check on this approach.

How do I get past this lifetime error?

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);


    // Initialization must be done every time you connect to a board.
    if mbl_mw_metawearboard_is_initialized(board) == 0 {
        mbl_mw_metawearboard_initialize(
            board,
            ptr::null_mut(),
            Some(status), // board is failing to initialize when status callback is called.
        );
    }


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

MblMwBtleConnection struct from generated bindings:

#[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,
        ),
    >,
...

#[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 future = self.read(&ac);
        // let task = tauri::async_runtime::spawn(future);
        // let output = tauri::async_runtime::block_on(async { task.await});


        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());

This version panicked during runtime about launching another runtime:

    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);

You can't nest block_on calls. I.e. a future you want to execute with block_on can't itself call block_on. It's hard for me to understand what is going on in your code snippets, but maybe that is something you do and can get rid of to eliminate the panic?

1 Like