Bindgen: convert `unsigned char data[1]` to `data: *mut u8` instead of `data: [u8; 1usize]`

Hi there,

I've already asked this on reddit but I'm thinking maybe more people will be able to help me here. If it's innappopriate, please pardon me and delete this post :slight_smile:

I'm doing a POC in my company (trying to make a shift in our technology) and I'm using bindgen to generate a Rust FFI from a massive in-house SDK.

I have a C struct in this SDK

typedef struct  _TheStruct
    UCHAR        Reserved [8];
    UINT            a;
    UCHAR        b;
    UCHAR        c;
    USHORT      len;
    UCHAR        data[1];
} TheStruct;

It's used like this:

TheStruct* s = (TheStruct*) malloc(sizeof(TheStruct) + length_of_data);

bindgen generate a [UCHAR; 1usize] as the field type for the Rust struct.

In Rust, how I'm suppose to allocate TheStruct with a length_of_data in order to put some data I got from a parameter:

pub fn send_data(code: u8, data: &[u8]) -> MyResult {
  let mut cmd: TheStruct = TheStruct {
    // easy fields
    data: // how to I stuck my parameter `data` in this data field?

Some people on reddit suggest using std::alloc but someone warned about alignment.

Thanks for any insight!

EDIT: the bindgenerated struct has #[repr(C, packed)]

I've done this so far:

let layout = Layout::from_size_align(
    mem::size_of::<TheStruct>() + data.len(),

unsafe {
    let myStruct = System.alloc_zeroed(layout) as *mut TheStruct;
    // other easy fields
    (*myStruct).dataLen = data.len() as u16;
    (*myStruct).data = // ???

I'm still stuck on how to put the parameter data: &[u8] in a [u8; 1] :frowning:

One question: the content of data will always be a char type (an array of them, surely) or it can change? Because in the latter case, you don't have a fixed data parameter, because you should have a proper padding depending on the alignment of that type.

Just another small note: if your original C struct is not packed, you should remove the repr(packed). On most of the platform you should not get any padding (except for the situation I was writing before), but it is better to have a perfect consistency between the C and the Rust struct.

Oh, last thing: I don't know if your SDK defines UCHAR, 'USHORT' (and so on) in term of uint8_t, uint16_t (...). If not so, remember that using u8, u16 (...) in Rust can lead to undefined behaviour on some esoteric platforms, because the size of C unsigned char, unsigned short can be different from what you (and me, and probably most of the people) normally expect them to be.

So, going back to the original problem, what you expect to be stored on data?

1 Like

Don't mess with alloc and layouts.

Use mem::zeroed() as the initialiser if you don't want to be bothered specifying all fields (e. g. when C has an init method).

Use Box::new(obj) to allocate memory for the struct, and then as_ptr() to get a temporary pointer to it (C borrows it) or Box::into_raw() to get a permanent pointer.

1 Like

Thank you for your help!

data is just bytes to be send.

someone came up with this on reddit:

EDIT: and the packed was generated by bindgen, so I'd rather not modify the manually to keep it automatically up to date

I can't do that as the data field is declared as [u8; 1] but the C code is using it by doing:

TheStruct* s = (TheStruct*) malloc(sizeof(TheStruct) + length_of_data);

which give some space for the data field

You can’t use Box::new to alloc because the struct size isn’t actually the required size - it needs to overallocate to get the dynamic size accounted for.

This would likely work better with custom DST support, alas that’s not available.

The Reddit solution is probably what I’d do as well given the circumstances.

1 Like

Out of curiosity, what't DST support?

DST = Dynamically Sized Type

1 Like

@vitalyd do you know if at least it is possible to unsafe impl !Sized for T ? Even if we still don't have custom DST, at least it will make the compiler complain if you try to use the struct on the stack...

It’s not possible to do that. The only negative trait impls can be for Sync and Send (and that’s an unstable feature).

One can define a DST struct today by having the last field be a DST itself but it’s a bit unwieldy to use and it’s not what bindgen generated anyway.

1 Like

Because it’s an inline allocation, not a ptr to memory allocated elsewhere.

Interesting idea!

Right, DST are a bit hellish most of the time. I ended up needing heterogeneous DST data in C++, and I am using a constexpr nightmare of structures to auto-generate the correct exception safe implementation (and the obvious unit tests to check if everything worked flawlessly :sob:).

I know that DST are not a priority for Rust at to date, but I hope to see something in the future, in order to have some safe abstractions for them with zero overhead.

1 Like