我正在尝试实现一个用 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()
完整的源代码在这里。
我截取了以下位置的屏幕截图:
TEST
和 TEST2
TEST
和TEST2
都存在良好的键值TEST2
值与键\0\0\0\0\0
(5个\0
字符)一起出现,TEST
值与键Ques
(先前消息中使用的Question
字的一部分)一起出现)TEST
、TEST2
和初始化键的哈希表。未找到 TEST
、TEST2
,但找到了初始化的键。。
您错误地处理了 Python 字符串:
c_char_p("TEST".encode())
没有 'static
生命周期。
我不理解这种行为,因为我将变量声明为
,所以 Rust 在进程完成之前不应释放内存。&'static str
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 需要清除数据时发生泄漏。