我有一个Java程序,它通过JNA调用Rust,为Rust端提供一个指针,指向一个可能很大(堆分配)的连续布局的缓冲区,\ 0终止的UTF-8字符串 . 内存由Java端拥有,并在垃圾收集器完成关联对象时释放 .
我的目标是通过将其解释为字符串向量来处理该缓冲区,执行我需要做的事情,并删除Rust在缓冲区顶部分配的所有结构,例如, Vec
's, String
s等 . 由于缓冲区的潜在大小,我希望尽可能避免复制数据 .
请考虑以下代码:
use std::ffi::CString;
use std::os::raw::c_char;
pub extern "C" fn process_data(data: *const c_char, num_elements: i64) {
let mut vec: Vec<String> = Vec::with_capacity(num_elements as usize);
let mut offset = 0;
unsafe {
for _ in 0..num_elements {
let ptr = { data.offset(offset as isize) };
// Main goal here is to have no memory copy involved
let s = String::from_utf8_unchecked(CString::from_raw(ptr as *mut c_char).into_bytes());
offset += s.len() + 1; // Include string termination
vec.push(s);
}
}
// do stuff with the vector
// ...
// Now that we're done, vec would be dropped, freeing the strings, thus freeing their underlying memory.
}
我的理解是我现在有一个 Vec
,它在内部指向一个包含 String
的缓冲区,然后内部指向 Vec
,然后以某种方式指向我传入的缓冲区 .
如果我让代码像这样运行而不会明确地忘记向量,我得到一个双重释放,因为Java试图释放缓冲区,但Rust已经通过删除向量来实现 . 说得通 . 但是,忘记向量会泄漏缓冲区顶部的所有“管理”结构 .
我想过如何在不泄漏任何内存的情况下解除Rust分配的所有内容 . 我想过明确地泄漏盒子并丢弃它们给我的指针(因为Java仍然有一个指针)沿着以下方向:
fn forget_vec(vec: Vec<String>) {
vec.into_iter().map(|s| {
Box::into_raw(s.into_bytes().into_boxed_slice());
}
}
但是,由于切片也是包含长度和指针的结构,并且通过执行上述操作,我认为我会泄漏此结构 . 我正在寻找消耗切片的东西,只返回像 *const u8
这样的指针 .
我有一种感觉,我通常会朝着正确的方向前进,但是我缺少一些重要的东西,或者对Rust的理解太少,无法让它完全正常工作 .
1 回答
重读CString的文档,强调我的:
你确实拥有这些字符串,Java也是如此 . 请改用
&str
和CStr:删除
Vec
时,它只删除引用,除了Vec
本身之外没有任何内容被释放 .