我想将结构数据传递给我自己的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 中维护数据的生命周期和所有权。有谁知道发生了什么事吗?我应该如何优化我的代码?
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
实际上被泄漏了(尽管您可以稍后重建它),并且可以自由使用相应的指针(当然,只要您确保访问同步并遵循引用别名规则)。