我正在围绕C API创建一个Rust包装器。此 C API 中的一个函数设置回调并接受将传递给回调的 void 指针。它存储对回调和用户数据的引用以供以后使用,因此我使用此答案中的最后一个代码部分。
这是我的代码。Test::trigger_callback(...)
函数旨在模拟调用回调的 C 库。
extern crate libc;
use libc::c_void;
use std::mem::transmute;
struct Test {
callback: extern "C" fn(data: i32, user: *mut c_void) -> (),
userdata: *mut c_void,
}
extern "C" fn c_callback(data: i32, user: *mut libc::c_void) {
unsafe {
println!("Line {}. Ptr: {}", line!(), user as u64);
let func: &mut Box<FnMut(i32) -> ()> = transmute(user);
println!("Line {}. Data: {:?}", line!(), data);
(*func)(data);
println!("Line {}", line!());
}
}
impl Test {
fn new<F>(func: F) -> Test
where
F: FnMut(i32) -> (),
F: 'static,
{
let func = Box::into_raw(Box::new(Box::new(func)));
println!("Line: {}, Ptr: {}", line!(), func as u64);
Test {
callback: c_callback,
userdata: func as *mut c_void,
}
}
fn trigger_callback(&self, data: i32) {
(self.callback)(data, self.userdata);
}
}
fn main() {
let test = Test::new(|data: i32| {
println!("Inside callback! Data: {}", data);
});
test.trigger_callback(12345);
}
如链接答案中所述,Box
关闭意味着将其存储在堆上,以便指向它的指针在任意长的时间内有效,然后Box
该Box
是因为它是一个胖指针,但需要转换为常规指针,以便它可以转换为 void 指针。
运行时,此代码将打印出来:
Line: 29, Ptr: 140589704282120
Line 13. Ptr: 140589704282120
Line 15. Data: 12345
Segmentation fault (core dumped)
尝试在extern "C"
函数内部调用闭包时,它会出现段错误。
为什么?据我了解,将闭包放在Box
中然后使用Box::into_raw(...)
应该将其存储在堆上并"泄漏"内存,因此只要程序正在运行,指针就应该有效。这其中哪一部分是错误的?
Box::into_raw(Box::new(Box::new(func)));
这不会产生您认为的类型:
= note: expected type `()`
found type `*mut std::boxed::Box<F>`
你假设它是一个特征对象:
let func: &mut Box<FnMut(i32) -> ()> = transmute(user);
相反,在装箱时将输入值转换为特征对象。我提倡明确的线条,并附上解释每个步骤的评论:
// Trait object with a stable address
let func = Box::new(func) as Box<FnMut(i32)>;
// Thin pointer
let func = Box::new(func);
// Raw pointer
let func = Box::into_raw(func);
Box<FnMut(i32) -> ()>
()
的返回类型是多余的;使用Box<FnMut(i32)>
let func: &mut Box<FnMut(i32) -> ()> = transmute(user);
非常努力地避免transmute
。通常有较小的工具可供使用:
extern "C" fn c_callback(data: i32, user: *mut libc::c_void) {
let user = user as *mut Box<FnMut(i32)>;
unsafe {
(*user)(data);
}
}
避免重述相同的类型。引入类型别名:
type CallbackFn = Box<FnMut(i32)>;
let user = user as *mut CallbackFn;
let func = Box::new(func) as CallbackFn;
另请参阅:
- 我的锈 FFI 综合巴士