为什么rust通过Box方法在堆上传递数据不是很可靠?

问题描述 投票:0回答:1

我想将结构数据传递给我自己的dll。为了确保数据不被丢弃,我使用

Box
将结构体移动到堆中,并通过
Box::into_raw
获取指针。不幸的是,有时当我从 dll 打印数据时会遇到一些“错误”。这很奇怪,因为ERROR随机发生。 我的

dll

代码如下:

#[repr(C)]
#[derive(Debug, Clone)]
pub enum MyValue {
    Bool(bool),
    String(*const i8),
}

#[repr(C)]
#[derive(Debug, Clone)]
pub struct StoreData {
    values: *const MyValue,
}

#[no_mangle]
extern "C" fn read_data(data: *const StoreData) {
    println!(" ");
    println!("==== in dll ====");
    println!("01 {:?}", data);
    unsafe {
        println!("02 {:?}", *(*data).values);
        // let MyValue::String(value) = *(*data).values.offset(0);
        let MyValue::String(value) = *(*data).values.offset(0) else {
            panic!("Dll Error")
        };
        println!("03 {:?}", value);
    };
    do something...
}

我的
main

代码如下,我使用

libloading =“0.8.4”
来加载我的dll: use libloading::{Library, Symbol}; use std::ffi::CString; #[repr(C)] #[derive(Debug, Clone)] pub enum MyValue { Bool(bool), String(*const i8), } #[repr(C)] #[derive(Debug, Clone)] pub struct StoreData { values: *const MyValue, } #[repr(C)] #[derive(Debug, Clone)] pub struct EntryPoint {} impl EntryPoint { pub fn init(values: Vec<String>) -> StoreData { let mut container = Vec::new(); for var in values { let c_var = CString::new(var.as_str()).unwrap(); container.push(MyValue::String(c_var.into_raw() as *const i8)); } StoreData { values: container.as_ptr(), } } pub fn call_dll(values: *mut StoreData) { type DLLFUNC = extern "C" fn(data: *const StoreData); unsafe { let lib = Library::new("something_dll.dll").unwrap(); let func: Symbol<DLLFUNC > = lib.get(b"read_data").unwrap(); func(values); lib.close().unwrap() } } } fn main() { let values = vec![String::from("hello"), String::from("world")]; let sd = EntryPoint::init(values); let sd_addr = Box::into_raw(Box::new(sd)); println!("==== main func get data ===="); println!("01 {:?}", sd_addr); unsafe { println!("02 {:?}", *(*sd_addr).values); // let MyValue::String(value) = *(*sd_addr).values.offset(0); let MyValue::String(value) = *(*sd_addr).values.offset(0) else { panic!("main ERROR") }; println!("03 {:?}", value); }; EntryPoint::call_dll(sd_addr); unsafe { let _ = Box::from_raw(sd_addr); } }

ERROR

信息如下: note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. fatal runtime error: Rust cannot catch foreign exceptions error: process didn't exit successfully: `target\debug\entry_point.exe` (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)

我发现当这个
ERROR

发生时,数据的struct内存地址确实发生了变化,像这样: ==== main func get data ==== 01 0x18f89dfe7f0 02 String(0x18f89dfe810) <- value real address. 03 0x18f89dfe810 ==== in dll ==== 01 0x18f89dfe7f0 02 String(0x18f89dfc2a0) <- value address has been changed.

我尝试在我的
MyValue

中只保留一个枚举值,如下所示:

#[repr(C)]
#[derive(Debug, Clone)]
pub enum MyValue {
    // Bool(bool),          <- comment out Bool
    String(*const i8),
}

我从 dll 打印的值始终是正确的。但我必须保留 
MyValue::Bool

因为我需要将一些 bool 类型值传递给我的 dll 来执行某些操作。 我不明白为什么会出现这个问题。在我的印象中,

Box

是一个非常可靠的工具,可以在 Rust 中维护数据的生命周期和所有权。有谁知道发生了什么事吗?我应该如何优化我的代码?

    

rust ffi box
1个回答
0
投票
EntryPoint::init

及之后使用原始指针而导致了 UB。

实际上,当我尝试重现该问题时,我有另一个输出:

==== main func get data ==== 01 0x55686e047a30 02 Bool(true) thread 'main' panicked at app/src/main.rs:59:13: main ERROR

 ...也就是说,我没有得到与我刚刚编写的相同的枚举变体。这严重暗示了某种未定义的行为,而且确实存在一种。

当您在

StoreData

末尾创建

EntryPoint::init
时,您使用
container.as_ptr
,它需要
&self
- 也就是说,它借用
Vec
,而不是消耗它。因此,
container
仍然在函数末尾的范围内,并且没有作为其返回值的一部分移出它 - 因此它被删除,并且
sd.values
变得悬空;取消引用它现在是释放后使用,并且可能会导致任何结果。
您可以尝试这种方法:

StoreData { values: Box::into_raw(container.into_boxed_slice()) as *const _, }

(最后一个 
as *const _

是必要的,因为

Box::into_raw
返回
*mut _
)。在这种情况下,
Box
实际上被泄漏了(尽管您可以稍后重建它),并且可以自由使用相应的指针(当然,只要您确保访问同步并遵循引用别名规则)。
    

© www.soinside.com 2019 - 2024. All rights reserved.