C中有一个 struct
struct Foo{
unsigned int body_len;
unsigned char body[1];
}
问题是如何定义struct foo的Rust部分,以及如何创建一个Foo实例,该实例将传递给外部C函数,该函数接受foo*作为参数,以便可以在C部分中访问主体数据.主体成员将在c函数中转换为(无符号字符*).
C中有一个 struct
struct Foo{
unsigned int body_len;
unsigned char body[1];
}
问题是如何定义struct foo的Rust部分,以及如何创建一个Foo实例,该实例将传递给外部C函数,该函数接受foo*作为参数,以便可以在C部分中访问主体数据.主体成员将在c函数中转换为(无符号字符*).
这个 struct 声明的theoretically个最好的Rust类比是:
#[repr(C)]
struct Foo {
body_len: std::ffi::c_uint,
body: [std::ffi::c_uchar],
}
这是一个custom dynamically sized type;编译器知道它的大小是不同的.然而,有一个问题:处理这类类型涉及存储长度in the pointer, not in the struct的"胖指针"或"宽指针".因此,您会发现传递&Foo
甚至*const Foo
很困难,因为编译器会要求您构造具有冗余长度的指针(使用当前稳定的操作实际上很难做到这一点).
因此,为了处理internally的指定长度(即,在同一 struct 中的一个字段中),您需要放弃并采取基本上与C代码中相同的方法,即提供一个标记大于Foo
的数据开始的伪字段.然而,与标准C不同,Rust不要求数组的大小至少为1,因此我们可以更合理地安排.
#[repr(C)]
struct Foo {
body_len: std::ffi::c_uint,
body: [std::ffi::c_uchar; 0],
}
然后,您可以使用指针操作来访问body
.注意,要做到这一点是相当棘手的;您应该要求对您实际编写的任何代码进行代码审查,并且还应该在Miri以下测试您的代码(尽可能避免实际的FFI部分),以便有足够的信心确保它是健全和正确的.
下面是一个完整的例子,说明了如何做到这一点,提供了一个安全的BoxFoo
类型,可以分配Foo
并分发指向它们的指针.我自己并不是这种特殊的指针诡计的专家,所以请不要认为这绝对是最好的方法.
use std::{alloc, ptr};
use std::ffi::{c_uint, c_uchar};
#[repr(C)]
struct Foo {
body_len: c_uint,
body: [c_uchar; 0],
}
/// An owner for a heap allocated [`Foo`] that understands its length.
/// (We can't use `Box` because it assumes that `size_of::<Foo>()` is the entire size.)
struct BoxFoo {
foo: *mut Foo,
}
impl BoxFoo {
/// Computes the layout for allocating a `Foo` of a given size.
fn layout(body_len: c_uint) -> alloc::Layout {
let byte_len = usize::try_from(body_len).expect("body_len overflowed usize");
let (layout, _) = alloc::Layout::new::<Foo>()
.extend(alloc::Layout::array::<c_uchar>(byte_len).unwrap())
.unwrap();
layout
}
pub fn new(bytes_to_copy: &[c_uchar]) -> Self {
let body_len: std::ffi::c_uint = bytes_to_copy.len().try_into().expect("body too long");
let layout = Self::layout(body_len);
// SAFETY:
// * The preconditions of `alloc::alloc()` are met because we know `Foo` has nonzero size
// even if its data is nonzero.
// * The pointer write operations write into memory we just allocated to be big enough.
let foo = unsafe {
// Allocate enough memory for the data.
let ptr: *mut Foo = alloc::alloc(layout).cast::<Foo>();
if ptr.is_null() {
alloc::handle_alloc_error(layout);
}
// Initialize the fixed part of the struct
ptr::write(ptr, Foo { body_len, body: [] });
// Initialize the variable length part
let body_array_ptr: *mut [c_uchar; 0] = ptr::addr_of_mut!((*ptr).body);
std::ptr::copy_nonoverlapping(
bytes_to_copy.as_ptr(),
body_array_ptr.cast::<c_uchar>(),
bytes_to_copy.len(),
);
ptr
};
Self { foo }
}
pub fn as_ptr(&self) -> *const Foo {
self.foo
}
pub fn body(&self) -> &[c_uchar] {
// SAFETY: `Foo` has the invariant that `body_len` shall be no longer than is
// actually allocated after the `Foo` structure.
unsafe {
std::slice::from_raw_parts(
ptr::addr_of!((*self.foo).body).cast::<c_uchar>(),
usize::try_from((*self.foo).body_len).expect("body_len overflowed usize"),
)
}
}
}
impl std::ops::Deref for BoxFoo {
type Target = Foo;
fn deref(&self) -> &Self::Target {
// SAFETY: It is an invariant of this type that the pointer points to a valid Foo.
unsafe { &*self.foo }
}
}
impl Drop for BoxFoo {
fn drop(&mut self) {
let layout = Self::layout(unsafe { (*self.foo).body_len });
// SAFETY: It is an invariant of this type that the pointer is a current allocation of
// this layout.
unsafe {
alloc::dealloc(self.foo.cast::<u8>(), layout);
}
}
}
#[test]
fn alloc_use_dealloc() {
let data = b"hello world";
let foo = BoxFoo::new(data);
assert_eq!(foo.body(), data);
drop(foo);
}