many ffi apis contains this common pattern: a struct fixed header fields and a variable length array of payload data, and the size of the payload is determined at runtime, something like:
typedef struct {
// other fields omitted
DWORD count;
SHORT data[1];
} FooList;
/// pass NULL for buffer to retrive required buffer size
BOOL GetFooList(__OUT __OPT FooList *buffer, __INOUT DWORD *buffer_size);
I would like to write
#[repr(C)]
struct FooList {
pub header: FooHeader,
pub data: [u16],
}
but the only way I know to get a Box<FooList>
is to coerce from types with statically known size like:
// this is for illustration only,
// to actually use `UnsizeCoerce`, you need the unsized and sized types both use the same generic type definition
#[repr(C)]
struct FooListKnownSize<const ARRAY_SIZE: usize> {
pub header: FooHeader,
pub data: [u16; ARRAY_SIZE],
}
I don't know how (or if) you can legitimately allocate a Box<FooList>
with runtime queried size requirement.
currently I just allocate raw bytes (with alignment) and reinterpret the raw bytes at calculated offset, like this:
struct FooHeader {
// other fields omitted
pub count: u32,
}
#[repr(transparent)]
struct FooListRaw([u8]);
// one possible alternative using `alloc`, but the idea is the same
// struct FooListRaw(*mut u8);
const ALIGNMENT: usize = 4;
impl FooListRaw {
pub fn header_mut(&mut self) -> &mut FooHeader {
// SAFETY:
// cast aligned pointer to the first field of a repr(C) struct
unsafe {
let (_prefix, header, _suffix) = self.0.align_to_mut::<FooHeader>();
&mut header[0]
}
}
pub fn data_mut(&mut self) -> &mut [u16] {
// SAFETY: ???
unsafe {
let ptr = self.0.as_mut_ptr();
// CAUTION: may need to take into account additional paddings
let offset = ptr.align_offset(ALIGNMENT) + size_of::<FooHeader>();
let len = self.header_mut().count as usize;
slice::from_raw_parts_mut(ptr.add(offset).cast(), len)
}
}
}
impl FooListRaw {
fn zeroed(nbytes: usize) -> Box<Self> {
let raw_storage = vec![0u8; nbytes + ALIGNMENT].into_boxed_slice();
// SAFETY: ???
// give `repr(transparent)` struct with single field, are we allowed to cast between fat boxes?
unsafe { transmute(raw_storage) }
}
/// SAFETY: do you trust the ffi api?
pub unsafe fn new() -> Option<Box<Self>> {
let mut len = 0;
// query size requirement first
if !ffi_get_foo_list(null_mut(), &mut len) {
return None;
}
// allocate
let mut raw = Self::zeroed(len as usize);
// initialize
if !ffi_get_foo_list(raw.header_mut(), &mut len) {
return None;
}
Some(raw)
}
}
this is bothering me for quite long, I'm always worried about the safety, as it's so delicate, and I have a lot of such types. so my question is:
is there obvious UB using this approach, and do you guys have better suggestions?
thanks in advance.