如何在Rust中包装pub extern "C" fn以调用结构体方法?
问题
需要包装一个pub extern "C" fn on_sync() { ... },使其被调用时能触发BluetoothDevice结构体impl中定义的pub fn on_sync(&self) { ... }方法。
现有代码
BluetoothDevice 结构体及实现
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) {} }
helpers 模块代码
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); }; } }
当前错误
helpers模块的set_device函数报错:lifetime may not live long enough assignment requires that "'a" must outlive "'static"。
需求
希望在Rust中实现类似C的解决方案(尽量避免将device设为static变量),无需通过C薄包装调用。C++参考代码如下:
#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; }
解决方案
方案1:模仿C++的回调+上下文参数模式
核心思路是存储回调函数与对应上下文(BluetoothDevice实例),C回调触发时通过上下文调用实例方法,用裸指针绕过Rust生命周期限制(需自行保证实例有效性)。
修改helpers模块如下:
use super::device; use core::ptr; // 定义适配C调用约定的回调类型 type SyncCallback = unsafe extern "C" fn(*const ()); type ResetCallback = unsafe extern "C" fn(i32, *const ()); struct CallbackStore { sync_cb: Option<SyncCallback>, sync_arg: *const (), reset_cb: Option<ResetCallback>, reset_arg: *const (), } static mut CALLBACK_STORE: CallbackStore = CallbackStore { sync_cb: None, sync_arg: ptr::null(), reset_cb: None, reset_arg: ptr::null(), }; // 包装BluetoothDevice的on_sync方法为C兼容函数 unsafe extern "C" fn wrap_on_sync(arg: *const ()) { let device = arg as *const device::BluetoothDevice<'static>; (*device).on_sync(); } // 包装BluetoothDevice的on_reset方法为C兼容函数 unsafe extern "C" fn wrap_on_reset(reason: i32, arg: *const ()) { let device = arg as *const device::BluetoothDevice<'static>; (*device).on_reset(); } pub extern "C" fn on_sync() { unsafe { if let Some(cb) = CALLBACK_STORE.sync_cb { cb(CALLBACK_STORE.sync_arg); } } } pub extern "C" fn on_reset(reason: i32) { unsafe { if let Some(cb) = CALLBACK_STORE.reset_cb { cb(reason, CALLBACK_STORE.reset_arg); } } } pub fn set_callbacks(device: &device::BluetoothDevice<'_>) { unsafe { CALLBACK_STORE.sync_cb = Some(wrap_on_sync); CALLBACK_STORE.sync_arg = device as *const _ as *const (); CALLBACK_STORE.reset_cb = Some(wrap_on_reset); CALLBACK_STORE.reset_arg = device as *const _ as *const (); } }
修改BluetoothDevice的init方法,添加回调设置:
pub fn init(&self) { unsafe { helpers::set_callbacks(self); esp_idf_sys::ble_hs_cfg.sync_cb = Some(helpers::on_sync); esp_idf_sys::ble_hs_cfg.reset_cb = Some(helpers::on_reset); // ... } }
注意:必须确保BluetoothDevice实例的生命周期覆盖所有可能触发C回调的时间段,否则会出现悬垂指针问题。
方案2:全局静态+原子指针(允许全局变量场景)
若可接受全局变量,用原子指针安全管理设备实例,规避生命周期报错:
use super::device; use core::sync::atomic::{AtomicPtr, Ordering}; static DEVICE_INSTANCE: AtomicPtr<device::BluetoothDevice<'static>> = AtomicPtr::new(core::ptr::null_mut()); pub extern "C" fn on_sync() { unsafe { let ptr = DEVICE_INSTANCE.load(Ordering::Acquire); if !ptr.is_null() { (*ptr).on_sync(); } } } pub extern "C" fn on_reset() { unsafe { let ptr = DEVICE_INSTANCE.load(Ordering::Acquire); if !ptr.is_null() { (*ptr).on_reset(); } } } pub fn set_device(device: &'static device::BluetoothDevice<'static>) { DEVICE_INSTANCE.store(device as *const _ as *mut _, Ordering::Release); }
说明:需将BluetoothDevice的生命周期改为'static,或通过Arc包裹内部数据延长生命周期,此方式安全性更高但依赖全局变量。
内容的提问来源于stack exchange,提问作者Mihai Bojescu
相关产品推荐
相关产品推荐

