如何释放Rust在FFI缓冲区顶部分配的所有结构,而不释放缓冲区本身?

时间:2018-10-16 19:37:57

标签: rust ffi

我有一个Java程序,该程序通过JNA调用Rust,为Rust端提供了指向潜在的(连续分配的,\ 0终止的)UTF-8字符串的大(堆分配)缓冲区的指针。内存归Java端所有,并在垃圾回收器最终确定关联对象后释放。

我的目标是通过将缓冲区解释为字符串向量来处理该缓冲区,执行我需要做的事情,然后将Rust分配的所有结构都放在缓冲区顶部,例如VecString等。由于缓冲区的大小,如果可能,我想避免在周围复制数据。

考虑以下代码:

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的了解太少,无法使其完全正常工作。

1 个答案:

答案 0 :(得分:5)

重新阅读CString的文档,重点是我的

  

一种类型,表示拥有的,C兼容的,以nul终止的字符串,中间没有nul字节。

     

此类型的目的是能够从Rust字节片或向量中安全生成C兼容字符串

拥有这些字符串,Java拥有。使用&strCStr代替:

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本身则不会被释放。