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

How can I free all structures that Rust allocated on top of an FFI buffer without freeing the buffer itself?

我有一个 Java 程序,它通过 JNA 调用 Rust,为 Rust 端提供一个指针,指向一个潜在的大(堆分配的)连续布局的缓冲区,\0 终止 UTF-8字符串。内存由 Java 方拥有,并在垃圾收集器完成关联对象时释放。

我的目标是通过将缓冲区解释为字符串向量来处理该缓冲区,执行我需要做的事情,并删除 Rust 在缓冲区顶部分配的所有结构,例如Vec's、Strings 等。由于缓冲区的潜在大小,我想尽可能避免复制数据。

考虑以下代码:

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 内部指向包含 Strings 的缓冲区,后者又在内部指向 Vecs,然后以某种方式指向进入我传入的缓冲区。

如果我让代码 运行 像这样而不显式忘记向量,我会得到双重释放,因为 Java 试图释放缓冲区,但 Rust 已经通过删除向量来这样做了。说得通。但是,忘记向量会泄漏缓冲区顶部的所有 "management" 结构。

我考虑了如何在不泄漏任何内存的情况下释放 Rust 分配的所有内容。我考虑过明确泄漏框并删除它们给我的指针(因为 Java 仍然有一个指针)沿着:

fn forget_vec(vec: Vec<String>) {
    vec.into_iter().map(|s| {
        Box::into_raw(s.into_bytes().into_boxed_slice());
    }
}

但是,由于切片也是一个包含长度和指针的结构,通过执行上述操作,我想我会泄露这个结构。我一直在寻找消耗切片的东西,只有 returns 我是一个像 *const u8.

这样的指针

我感觉我大体上是在朝着正确的方向前进,但是我遗漏了一些重要的东西或者对 Rust 的理解太少而无法让它完全发挥作用。

重新阅读 CString 的文档,强调我的:

A type representing an owned, C-compatible, nul-terminated string with no nul bytes in the middle.

This type serves the purpose of being able to safely generate a C-compatible string from a Rust byte slice or vector.

拥有这些字符串,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 本身之外没有任何东西被释放。