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

时间:2018-06-20 19:22:44

标签: rust segmentation-fault ffi

我正在围绕C API创建Rust包装器。此C API中的一个函数设置了一个回调并接受一个void指针,该指针将传递给该回调。它存储了对回调和用户数据的引用,供以后使用,因此我正在使用this answer中的最后一个代码部分。

这是我的代码。 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是因为它是一个胖指针,但需要将其转换为常规指针,以便可以将其强制转换为空指针。

运行时,此代码会打印出来:

Line: 29, Ptr: 140589704282120
Line 13. Ptr: 140589704282120
Line 15. Data: 12345
Segmentation fault (core dumped)

尝试在extern "C"函数内部调用闭包时,它将出现段错误。

为什么?据我了解,将闭包放在Box中,然后使用Box::into_raw(...)应该将其存储在堆上并“泄漏”内存,因此指针应在程序中有效在跑。哪一部分错了?

1 个答案:

答案 0 :(得分:1)

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;

另请参阅: