我有一个Java程序,该程序通过JNA调用Rust,为Rust端提供了指向潜在的(连续分配的,\ 0终止的)UTF-8字符串的大(堆分配)缓冲区的指针。内存归Java端所有,并在垃圾回收器最终确定关联对象后释放。
我的目标是通过将缓冲区解释为字符串向量来处理该缓冲区,执行我需要做的事情,然后将Rust分配的所有结构都放在缓冲区顶部,例如Vec
,String
等。由于缓冲区的大小,如果可能,我想避免在周围复制数据。
考虑以下代码:
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
s的缓冲区,而该缓冲区又内部指向Vec
s,然后以某种方式指向该缓冲区。我传入的缓冲区。
如果我让代码这样运行而不显式地忘记向量,我将获得双重释放,因为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的了解太少,无法使其完全正常工作。
答案 0 :(得分:5)
重新阅读CString
的文档,重点是我的
一种类型,表示拥有的,C兼容的,以nul终止的字符串,中间没有nul字节。
此类型的目的是能够从Rust字节片或向量中安全生成C兼容字符串。
您不拥有这些字符串,Java拥有。使用&str
和CStr
代替:
use std::ffi::CStr;
use std::os::raw::c_char;
pub extern "C" fn process_data(data: *const c_char, num_elements: i64) {
let mut vec: Vec<&str> = Vec::with_capacity(num_elements as usize);
unsafe {
let mut ptr = data;
for _ in 0..num_elements {
let s = CStr::from_ptr(ptr);
ptr = ptr.add(s.to_bytes().len() + 1); // Include string termination
if let Ok(s) = s.to_str() {
vec.push(s);
}
}
}
}
当您的Vec
被删除时,它只会删除引用,而Vec
本身则不会被释放。