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

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

问题描述

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

这是我的代码.Test::trigger_callback(...) 函数旨在模拟调用回调的 C 库.

extern crate libc;使用 libc::c_void;使用 std::mem::transmute;结构测试{回调:外部C"fn(数据:i32,用户:*mut c_void)->(),用户数据:*mut c_void,}extern "C" fn c_callback(data: i32, user: *mut libc::c_void) {不安全{println!("Line {}. Ptr: {}", line!(), user as u64);let func: &mut Box<FnMut(i32) ->()>= 转化(用户);println!("行{}.数据:{:?}", line!(), data);(*func)(数据);println!("行 {}", line!());}}实现测试{fn new<F>(func:F) ->测试在哪里F: FnMut(i32) ->(),F:'静态的,{让 func = Box::into_raw(Box::new(Box::new(func)));println!("Line: {}, Ptr: {}", line!(), func as u64);测试 {回调:c_callback,用户数据:函数为 *mut c_void,}}fn trigger_callback(&self, data: i32) {(self.callback)(数据, self.userdata);}}fn 主要() {让 test = Test::new(|data: i32| {println!("内部回调!数据:{}", data);});test.trigger_callback(12345);}

如链接答案中所述,Box 闭包意味着将其存储在堆上,以便指向它的指针在任意长时间内有效,然后 BoxBox 是因为它是一个胖指针,但需要转换为常规指针才能转换为 void 指针.

运行时,打印出这段代码:

Line: 29, Ptr: 140589704282120第 13 行.联系人:140589704282120第 15 行.数据:12345分段错误(核心转储)

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

为什么?据我了解,将闭包放在 Box 中,然后使用 Box::into_raw(...) 应该将其存储在堆上并泄漏"内存,因此只要程序正在运行,指针就应该有效.这有什么问题?

解决方案

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

这不会产生你认为的类型:

 = 注意:预期类型`()`找到类型`*mut std::boxed::Box<F>`

你假设它是一个特征对象:

<块引用>

let func: &mut Box<FnMut(i32) ->()>= 转化(用户);

取而代之的是,当您将输入值装箱时,将其转换为一个 trait 对象.我提倡明确的行,并带有解释每个步骤的注释:

//具有稳定地址的特征对象让 func = Box::new(func) as Box;//细指针让 func = Box::new(func);//原始指针让 func = Box::into_raw(func);

<小时><块引用>

Box()>

()的返回类型是多余的;使用 Box

<块引用>

let func: &mut Box<FnMut(i32) ->()>= 转化(用户);

尝试真的努力避免transmute.通常可以使用较小的工具:

extern "C" fn c_callback(data: i32, user: *mut libc::c_void) {let user = user as *mut Box<FnMut(i32)>;不安全{(*用户数据);}}

避免重述同一类型.引入一个类型别名:

type CallbackFn = Box;

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天全站免登陆