使用C回调用户数据存储盒装Rust闭包时出现分段错误 [英] Segmentation fault when using C callback user data to store a boxed Rust closure

查看:83
本文介绍了使用C回调用户数据存储盒装Rust闭包时出现分段错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是我的代码. 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(...)应该将其存储在堆上并泄漏"内存,因此指针应在程序运行期间一直有效.哪一部分错了?

解决方案

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;

另请参阅:

I am creating a Rust wrapper around a C API. One function in this C API sets a callback and accepts a void pointer which will be passed to the callback. It stores a reference to the callback and user data for later, so I am using the last code section from this answer.

Here is my code. The Test::trigger_callback(...) function is meant to emulate the C library calling the callback.

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);
}

As mentioned in the linked answer, Boxing the closure is meant to store it on the heap so that a pointer to it will be valid for an arbitrarily long time, and then Boxing that Box is because it's a fat pointer, but needs to be converted to a regular pointer so that it can be cast to a void pointer.

When run, this code prints out:

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

It segfaults when trying to call the closure inside of the extern "C" function.

Why? As far as I understand it, putting the closure in a Box and then using Box::into_raw(...) should store it on the heap and 'leak' the memory, so the pointer should be valid for as long as the program is running. What part of this is wrong?

解决方案

Box::into_raw(Box::new(Box::new(func)));

This does not produce the type you think it does:

   = note: expected type `()`
              found type `*mut std::boxed::Box<F>`

You assume it's a trait object:

let func: &mut Box<FnMut(i32) -> ()> = transmute(user);

Instead, make the input value into a trait object when you box it. I advocate for explicit lines with comments explaining each step:

// 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) -> ()>

The return type of () is redundant; use Box<FnMut(i32)>

let func: &mut Box<FnMut(i32) -> ()> = transmute(user);

Try really hard to avoid transmute. There are usually smaller tools to use:

extern "C" fn c_callback(data: i32, user: *mut libc::c_void) {
    let user = user as *mut Box<FnMut(i32)>;
    unsafe {
        (*user)(data);
    }
}

Avoid restating the same type all over. Introduce a type alias:

type CallbackFn = Box<FnMut(i32)>;

let user = user as *mut CallbackFn;

let func = Box::new(func) as CallbackFn;

See also:

这篇关于使用C回调用户数据存储盒装Rust闭包时出现分段错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆