使用 C 回调用户数据存储盒装 Rust 闭包时的分段错误



我正在围绕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关闭意味着将其存储在堆上,以便指向它的指针在任意长的时间内有效,然后BoxBox是因为它是一个胖指针,但需要转换为常规指针,以便它可以转换为 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 综合巴士

相关内容

  • 没有找到相关文章

最新更新