Почему Rust не очень надежно передает данные в куче методом Box?

Я хочу передать данные структуры в свою собственную 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. Кто-нибудь знает, что происходит? И как мне оптимизировать свой код?

Ваша проблема стоит намного раньше любого Box-а. Ваш EntryPoint::init ничего не поддерживает: CString вы получаете указатели на уничтожение в конце каждой итерации, а вы получаете только указатель на container, который уничтожается в конце функции.

kmdreko 24.06.2024 05:34
CString не уничтожаются, так как их не роняют, а съедают into_raw. container действительно уничтожается, поскольку as_ptr не потребляет Vec, это временное представление.
Cerberus 24.06.2024 05:37

Это та же проблема, что и в вашем предыдущем вопросе

cafce25 24.06.2024 08:23
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Причина этой ошибки в том, что вы нажимаете 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.

Jayden 24.06.2024 06:13

Я думаю, вам следует использовать .leak() вместо .into_boxed_slice и into_raw.

kmdreko 24.06.2024 06:17

Я не был уверен, что leak будет работать правильно в отношении псевдонимов ссылок. into_raw не проходит по ссылке, так что это, вероятно, более надежно.

Cerberus 24.06.2024 06:52

Другие вопросы по теме