如何使用 Rust 在 DLL 中为全局和可变哈希表释放一个键(字符串)?

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

我正在尝试实现一个用 Rust 编写的跨平台 DLL/SharedObject。我在这个库中初始化了一个全局且可变的哈希表。当我在此哈希表中添加键和值时,键(a

&'static str
)被释放,用
\0
字符重置并重新分配给其他用途。我不理解这种行为,因为我将变量声明为
&'static str
,因此 Rust 在进程完成之前不应释放内存。你能向我解释一下这种行为吗?你知道解决问题的正确方法吗?

我在可执行文件中没有此行为,仅在 DLL 中。

Rust DLL/共享对象代码:

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()

完整的源代码在这里

我截取了以下位置的屏幕截图:

  1. 我添加了 2 个名为
    TEST
    TEST2
  2. 的键和值
  3. 我列出了所有键和值,
    TEST
    TEST2
    都存在良好的键值
  4. 我使用我的库和带有初始化键的哈希表,这工作得很好
  5. 我列出了所有键和值,
    TEST2
    值与键
    \0\0\0\0\0
    (5个
    \0
    字符)一起出现,
    TEST
    值与键
    Ques
    (先前消息中使用的
    Question
    字的一部分)一起出现)
  6. 我使用我的库和带有
    TEST
    TEST2
    和初始化键的哈希表。未找到
    TEST
    TEST2
    ,但找到了初始化的键。

ScreenShot

rust memory dll hashtable global
1个回答
0
投票

您错误地处理了 Python 字符串:

c_char_p("TEST".encode())
没有
'static
生命周期。

我不理解这种行为,因为我将变量声明为

&'static str
,所以 Rust 在进程完成之前不应释放内存。

Rust 无法控制从其他语言传递给它的内存,将其注释为

'static
不会改变任何内容。 Rust 的生命周期是“描述性的”而不是“规定性的”,并且您错误地描述了生命周期。 from_raw_parts 的部分安全要求是推导的生命周期不长于底层对象的存活时间。如果你确实保证 Python 字符串会无限期地持续存在,那么这是一回事,但你不是。 Rust 不会释放内存,但 Python 会,并且用于字符串的内存将由 GC 回收并重用(或者显然在本例中用零清除),而 Rust 仍然有一个指向它所在位置的指针是。
您必须通过创建一个 

owned

String

来保证自己,然后 leak() 使其成为真正的

&'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.into_owned();
    String::leak(string_r)
}
尽管此时您应该从一开始就让您的 

HashMap
 存储 
String

(或至少

Cow<str, 'static>
),以避免在您的 API 需要清除数据时发生泄漏。
    

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