如何在Rust的DLL中为全局和可变的HashTable释放一个键(字符串)?

h5qlskok  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(91)

我正在尝试实现一个用Rust编写的跨平台DLL/SharedObject。我在这个库中初始化了一个全局可变的哈希表。当我在这个哈希表中添加一个键和值时,键(&'static str)被释放,用\0字符重置,并重新分配给另一个用途。我不理解这种行为,因为我将变量声明为&'static str,所以Rust在进程完成之前不应该释放内存。你能给我解释一下你的行为吗?你知道解决这个问题的正确方法吗?
我在可执行文件中没有这种行为,只有在DLL中。
代码Rust DLL/SharedObject:

use std::{sync::Mutex, collections::HashMap};
use lazy_static::lazy_static;
use std::io::{stdout, Write};

#[derive(Clone)]
enum Color {
    Red,
}

impl Color {
    fn value(&self) -> i32 {
        match *self {
            Color::Red    => 1,
        }
    }
}

lazy_static! {
    static ref STATE_OK: State = {
        State {
            name: String::from("OK"),
            color: Color::Green,
            character: "+".to_string(),
        }
    };
    static ref STATES: Mutex<HashMap<&'static str, Box<dyn _State + Send>>> = {
        let _states: HashMap<&'static str, Box<dyn _State + Send>> = HashMap::from(
            [
                (
                    "OK",
                    Box::new(STATE_OK.clone()) as Box<dyn _State + Send>
                )
            ]
        );
        Mutex::new(_states)
    };
}

fn rust_from_c_string (string_c: *const u8) -> &'static str {
    let length = strlen(string_c);
    let slice = unsafe { std::slice::from_raw_parts(string_c, length) };
    std::str::from_utf8(slice).unwrap()
}

fn strlen(string_c: *const u8) -> usize {
    let mut length = 0;
    unsafe {
        while *string_c.add(length) != 0 {
            length += 1;
        }
    }
    length
}

#[no_mangle]
pub extern "C" fn add_state (key_: *const u8, character_: *const u8, color_: *const u8) {
    let key: &'static str = rust_from_c_string(key_);
    let color = rust_from_c_string(color_);
    let character = rust_from_c_string(character_);

    let mut _states = STATES.lock().unwrap();
    _states.insert(
        key,
        Box::new(State {
            name: String::from(key),
            color: match color {
                "red"    => Color::Red,
            },
            character: String::from(character),
        }) as Box<dyn _State + Send>
    );
}

#[no_mangle]
pub extern "C" fn messagef (text_: *const u8, state_: *const u8) {
    let text = rust_from_c_string(text_);
    let state = rust_from_c_string(state_);
    let to_print: String;
    let _states = STATES.lock().unwrap();
    let state = _states.get(&*state.unwrap_or("OK").to_string()).unwrap();
    print!("{}", to_print);
    let _ = stdout().flush();
}

#[no_mangle]
pub extern "C" fn print_all_state () {
    DEFAULT_STATE.lock().unwrap().print("not found");

    let _states = STATES.lock().unwrap();

    for (key, state) in _states.iter() {
        state.print(key);
    }
}

从Python中调用:

from ctypes import c_char_p, c_ubyte, c_ushort, pointer
from os.path import join, dirname, exists
from os import name, getcwd

if name == "nt":
    from ctypes import windll as sysdll

    filename = "TerminalMessages.dll"
else:
    from ctypes import cdll as sysdll

    filename = "libTerminalMessages.so"

filenames = (join(dirname(__file__), filename), join(getcwd(), filename))
for filename in filenames:
    if exists(filename):
        break
else:
    raise FileNotFoundError(f"Library {filename!r} is missing")

lib = sysdll.LoadLibrary(filename)

lib.print_all_state()

lib.add_state(
    c_char_p("TEST".encode()),
    c_char_p("T".encode()),
    c_char_p("red".lower().encode()),
)

lib.messagef(
    c_char_p("test".encode()),
    c_char_p("TEST".encode()),
)

lib.print_all_state()

完整的源代码是here
我截图如下:
1.我添加了两个键和值,分别命名为TESTTEST2

  1. i列出了所有的键和值,TESTTEST2都有正确的键值
    1.我使用我的库和哈希表与初始化的关键字,这是工作良好
    1.我列出了所有键和值,TEST2值与键\0\0\0\0\0(5个\0字符)一起出现,TEST值与键Ques(先前消息中使用的Question单词的一部分)一起出现。
    1.我使用我的库和哈希表与TESTTEST2和初始化的关键字。未找到TESTTEST2,但找到了初始化的密钥。


amrnrhlw

amrnrhlw1#

你错误地处理了Python字符串:c_char_p("TEST".encode())没有'static生命周期。
我不理解这种行为,因为我将变量声明为&'static str,所以Rust在进程完成之前不应该释放内存。
Rust无法控制从其他语言传递给它的内存,将其注解为'static不会改变任何东西。Rust的生命周期是 * 描述性的 *,而不是 * 规定性的 *,你对生命周期的描述是错误的。from_raw_parts的部分安全性要求是推断的生存期不长于底层对象的生存期。如果你确实保证了Python字符串将无限期地持久化,那么这是一回事,但你没有。
Rust没有释放内存,但Python是,用于字符串的内存将被GC回收并重用(或者显然在这种情况下用零清除),而Rust仍然有一个指针指向它所在的位置。
您必须通过创建一个 ownedString然后leak()-ing它来创建一个真正的&'static str来保证这一点。大概是这样的:

fn rust_from_c_string (string_c: *const u8) -> &'static str {
    let length = strlen(string_c);
    let slice = unsafe { std::slice::from_raw_parts(string_c, length) };
    let str_r = std::str::from_utf8(slice).unwrap();
    let string_r = str_r.to_owned();
    String::leak(string_r)
}

尽管在这一点上,您应该让您的HashMap从一开始就存储String s(或至少Cow<str, 'static>),以避免在您的API需要清除数据时泄漏。

相关问题