如何将 Rust 的 `Vec<T>` 暴露给 FFI?

32

我正尝试构建一对元素:

  • array: *mut T
  • array_len: usize

array 旨在拥有数据。

然而,Box::into_raw 将返回 *mut [T]。我找不到任何关于将裸指针转换为切片的信息。它在内存中的布局是什么?我如何从 C 中使用它?是否应该转换为 *mut T ?如果是这样,如何转换?

2个回答

37

如果你只想要一些用于可变借用 Vec 的 C 函数,你可以这样做:

extern "C" {
    fn some_c_function(ptr: *mut i32, len: ffi::size_t);
}

fn safe_wrapper(a: &mut [i32]) {
    unsafe {
        some_c_function(a.as_mut_ptr(), a.len() as ffi::size_t);
    }
}

当然,C函数不应该将这个指针存储在其他地方,因为那样会破坏别名假设。

如果你想要将数据的“所有权”传递给C代码,你可以这样做:

use std::mem;

extern "C" {
    fn c_sink(ptr: *mut i32, len: ffi::size_t);
}

fn sink_wrapper(mut vec: Vec<i32>) {
    vec.shrink_to_fit();
    assert!(vec.len() == vec.capacity());
    let ptr = vec.as_mut_ptr();
    let len = vec.len();
    mem::forget(vec); // prevent deallocation in Rust
                      // The array is still there but no Rust object
                      // feels responsible. We only have ptr/len now
                      // to reach it.
    unsafe {
        c_sink(ptr, len as ffi::size_t);
    }
}

在这里,“接管所有权”的意思是我们期望C函数最终通过调用一个Rust函数来释放指针和长度,将其返回给Rust:

#[no_mangle]
/// This is intended for the C code to call for deallocating the
/// Rust-allocated i32 array.
unsafe extern "C" fn deallocate_rust_buffer(ptr: *mut i32, len: ffi::size_t) {
    let len = len as usize;
    drop(Vec::from_raw_parts(ptr, len, len));
}

由于 Vec::from_raw_parts 需要三个参数,即指针、大小和容量,因此我们要么以某种方式跟踪容量,要么在将指针和长度传递给 C 函数之前使用 Vec 的 shrink_to_fit。但这可能涉及重新分配内存。


这就是我最终使用的:https://github.com/maidsafe/safe_core/pull/321/files#diff-6c72b345033f35005d017f9f5a1e1c9eR60。除了assert!之外,我还在考虑使用它,但我不够自信/说服。 - vinipsmaker
我不知道为什么,但如果您有一个长度/容量/大小为0的Vec,并且执行.as_mut_ptr()操作,它会返回0x1(即无效指针)。 - Brandon Ros
1
Vec::shrink_to_fit 的文档表明,你的断言 assert!(vec.len() == vec.capacity()); 并不总是正确的(文档只断言 capacity() >= len())。这个问题 本质上涉及到了同样的问题,因此我在那里提到了这个答案,但该问题具体涉及到 shrink_to_fit 不精确的可能性,这似乎没有在这里得到解决,但似乎也与此相关。 - kaya3

10
你可以使用 [T]::as_mut_ptr直接从Vec<T>Box<[T]>或任何其他DerefMut-to-slice类型中获取*mut T指针。
use std::mem;

let mut boxed_slice: Box<[T]> = vector.into_boxed_slice();

let array: *mut T = boxed_slice.as_mut_ptr();
let array_len: usize = boxed_slice.len();

// Prevent the slice from being destroyed (Leak the memory).
mem::forget(boxed_slice);

@vinipsmaker:不要这样做。因此,请通过不使用“forget”来防止向量被销毁。请参见更新。 - kennytm
1
抱歉,我对这种方法中的释放代码如何工作感到困惑。into_boxed_slice会返回什么内存布局?boxed_slice.as_mut_ptr()保证返回指向第一个字符的指针吗?我该如何转换回Box<[T]>以便进行解除分配? - vinipsmaker
2
@vinipsmaker:(1)布局未指定。当前实现使用(ptr,len)。(2)也许你应该提出一个新问题。但你可以尝试使用slice::from_raw_parts_mutBox::from_raw,或者使用Vec::from_raw_parts,但你需要传递容量。 - kennytm
1
slice::from_raw_parts + Box::from_raw 在这里可以吗?Box::from_raw 不是获取指向栈分配的切片的指针而不是原始切片吗?或者 Box<[T]> 是一个特殊情况? - vinipsmaker
@vinipsmaker:slice::from_raw_parts_mut(array, array_len) 的结果将引用你的堆分配内存。在这里,没有涉及任何数组的堆栈分配。而 Box::from_raw 将再次拥有它,因此在它被丢弃时,它将被适当地释放。但是,所有这些基本上都归结为 Vec::from_raw_parts(array, array_len, array_len),向 vec 再次转移了拥有权。顺便说一下:在这里使用 into_boxed_slice 是很重要的,因为它会减小容量以匹配大小。否则,你还需要记住原始容量。 - sellibitze
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接