Я хочу передать данные структуры в свою собственную dll. Чтобы гарантировать, что данные не будут удалены, я использую Box
, чтобы переместить структуру в кучу, и получаю указатель по Box::into_raw
. К сожалению, иногда я сталкиваюсь с ОШИБКОЙ при печати данных из моей dll. Это очень странно, потому что ОШИБКА возникает случайным образом.
Мои 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);
}
}
Информация об ОШИБКЕ следующая:
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)
Я обнаружил, что при возникновении этой ОШИБКИ адрес структурной памяти данных действительно меняется, например:
==== 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. Кто-нибудь знает, что происходит? И как мне оптимизировать свой код?
CString
не уничтожаются, так как их не роняют, а съедают into_raw
. container
действительно уничтожается, поскольку as_ptr
не потребляет Vec
, это временное представление.
Это та же проблема, что и в вашем предыдущем вопросе
Причина этой ошибки в том, что вы нажимаете UB из-за использования необработанных указателей в EntryPoint::init
и после него.
На самом деле, когда я пытаюсь воспроизвести проблему, у меня есть другой вывод:
==== 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
фактически просачивается (хотя вы можете восстановить его позже), и соответствующий указатель можно использовать свободно (конечно, если вы убедитесь, что доступ синхронизирован и правила псевдонимов ссылок соблюдаются).
спасибо за ответ. То, как вы это сказали, очень полезно. Ошибка 02 Bool(true)thread 'main' panicked at app/src/main.rs:59:13:main ERROR
. Я думаю, вы прокомментировали только MyValue::Bool в main.rs, но не прокомментировали предложение в lib.rs.
Я думаю, вам следует использовать .leak()
вместо .into_boxed_slice
и into_raw
.
Я не был уверен, что leak
будет работать правильно в отношении псевдонимов ссылок. into_raw
не проходит по ссылке, так что это, вероятно, более надежно.
Ваша проблема стоит намного раньше любого
Box
-а. ВашEntryPoint::init
ничего не поддерживает:CString
вы получаете указатели на уничтожение в конце каждой итерации, а вы получаете только указатель наcontainer
, который уничтожается в конце функции.