How do I wrap a pub extern "C" fn such that such that I can call a method of a struct?

162 Views Asked by At

I have hit a wall recently. The summary would be the following: I would like to wrap a pub extern "C" fn on_sync() { ... } such that when it is called I can also call a method named pub fn on_sync(&self) { ... } defined in an impl of a struct BluetoothDevice.

The struct BluetoothDevice and its impl can be found below:

use super::helpers;
use crate::lib::log::log::Logger;
use esp_idf_sys;

extern crate alloc;

pub struct BluetoothDevice<'a> {
    logger: &'a Logger,
}

impl<'a> BluetoothDevice<'a> {
    pub fn new(logger: &'a Logger) -> Self {
        BluetoothDevice { logger }
    }

    pub fn init(&self) {
        unsafe {
            // ...

            esp_idf_sys::ble_hs_cfg.sync_cb = Some(helpers::on_sync);
            esp_idf_sys::ble_hs_cfg.reset_cb = Some(helpers::on_reset);

            // ...
        }
    }

    pub fn on_sync(&self) {}

    pub fn on_reset(&self) {}
}

And the helpers module can be found below:

use super::device;

struct BluetoothGAPWorkaround<'a> {
    instance: Option<&'a device::BluetoothDevice<'a>>,
}

static mut BLUETOOTH_GAP_WORKAROUND: BluetoothGAPWorkaround =
    BluetoothGAPWorkaround { instance: None };

pub extern "C" fn on_sync() {
    unsafe {
        match BLUETOOTH_GAP_WORKAROUND.instance {
            Some(device) => device.on_sync(),
            None => return,
        }
    }
}

pub extern "C" fn on_reset() {
    unsafe {
        match BLUETOOTH_GAP_WORKAROUND.instance {
            Some(device) => device.on_reset(),
            None => return,
        }
    }
}

pub fn set_device<'a>(bluetoothDevice: &'a device::BluetoothDevice<'a>) {
    unsafe {
        if let None = BLUETOOTH_GAP_WORKAROUND.instance {
            BLUETOOTH_GAP_WORKAROUND.instance = Some(bluetoothDevice);
        };
    }
}

The problem with the Rust helpers module is the fact that I keep hitting a wall in the pub fn set_device<'a>(...) { ... } function due to the following error: lifetime may not live long enough assignment requires that "'a" must outlive "'static".

Would there be any solution of wrapping pub extern "C" on_sync() { ... } such that I can call device.on_sync(), preferrably without having to set the device as a static variable?

Previously I hit this issue in C++ too and I fixed it with the following and would like to perform the same thing in Rust:

#include "bluetoothGAPWorkarounds.h"
#include <host/ble_hs.h>

struct BluetoothGAPWorkaround
{
    boundOnSyncCallback *onSyncCallback;
    boundOnResetCallback *onResetCallback;
    void *onSyncCallbackArg;
    void *onResetCallbackArg;
} workaround;

static void unboundOnSyncCallback(void)
{
    if (!workaround.onSyncCallback)
    {
        return;
    }

    workaround.onSyncCallback(&workaround.onSyncCallbackArg);
}

static void unboundOnResetCallback(int reason)
{
    if (!workaround.onResetCallback)
    {
        return;
    }

    workaround.onResetCallback(reason, &workaround.onResetCallbackArg);
}

void setOnSyncCallback(boundOnSyncCallback callback, void *arg)
{
    workaround.onSyncCallback = callback;
    workaround.onSyncCallbackArg = arg;
    ble_hs_cfg.sync_cb = unboundOnSyncCallback;
}

void setOnResetCallback(boundOnResetCallback callback, void *arg)
{
    workaround.onResetCallback = callback;
    workaround.onResetCallbackArg = arg;
    ble_hs_cfg.reset_cb = unboundOnResetCallback;
}

I needed I can use the code above in a thin C++ wrapper that I can access from the Rust code, but that would not be ideal.

0

There are 0 best solutions below