Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allocate struct with ANYSIZE_ARRAY?

Tags:

rust

winapi

ffi

I'm working on HID Device API in Rust. I'm stuck on trying to get device path using Windows API.

I'm using the winapi crate function SetupDiGetDeviceInterfaceDetailA that takes pointer to struct SP_DEVICE_INTERFACE_DETAIL_DATA_A, which should be populated with device path, however the DevicePath field is an array of fixed size 1 (ANYSIZE_ARRAY).

STRUCT! {
    #[cfg_attr(target_arch = "x86", repr(packed))]
    struct SP_DEVICE_INTERFACE_DETAIL_DATA_A {
        cbSize: DWORD,
        DevicePath: [CHAR; ANYSIZE_ARRAY],
    }
}

GetLastError returns ERROR_INSUFFICIENT_BUFFER which totally makes sense.

What is proper way of allocating memory for a struct in such case ?

like image 971
Midi Avatar asked May 07 '26 16:05

Midi


1 Answers

Not sure about ‘proper’, but then Rust does not exactly have good support for dynamically-sized structures; this may change when work on DynSized (which in its current state can be described as ‘not fully defined’) is finalized. For now, expect lots of unsafe.

Going by Microsoft’s documentation for SetupDiGetDeviceInterfaceDetailA:

Using this function to get details about an interface is typically a two-step process:

  1. Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with a NULL DeviceInterfaceDetailData pointer, a DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize variable. In response to such a call, this function returns the required buffer size at RequiredSize and fails with GetLastError returning ERROR_INSUFFICIENT_BUFFER.
  2. Allocate an appropriately sized buffer and call the function again to get the interface details.

As such, first you need to query the size you need to allocate for the structure.

use std::{ptr, mem};

let required_size = {
    let mut required_size = mem::MaybeUninit::uninit();

    let ok = SetupDiGetDeviceInterfaceDetailA(
        device_info_set,
        device_iface_data,
        ptr::null_mut(),
        0,
        required_size.as_mut_ptr(),
        ptr::null_mut()
    );
    
    assert!(!ok && GetLastError() == ERROR_INSUFFICIENT_BUFFER);

    required_size.assume_init()
};

Then, allocate the buffer to contain the structure. Below, I use Vec<u8>. Because Vec is not guaranteed to allocate memory to any given alignment, I take account of that by adding some padding and offsetting the pointer. (In practice, I would expect the offset to be zero most of the time, but I cannot rely on that.)

let mut buf: Vec<u8> = Vec::with_capacity(
    TryInto::<usize>::try_into(required_size).unwrap() +
    mem::align_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>() - 1);

let align_offset = 
    buf.as_mut_ptr().align_offset(
        mem::align_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>());

assert!(
    align_offset < (buf.capacity() -
        TryInto::<usize>::try_into(required_size).unwrap())
);

let device_iface_detail = unsafe {
    &mut *(buf.as_mut_ptr().offset(align_offset.try_into().unwrap())
        as *mut mem::MaybeUninit<SP_DEVICE_INTERFACE_DETAIL_DATA_A>)
};

After allocating, fill the buffer with initial data:

device_iface_detail.write(SP_DEVICE_INTERFACE_DETAIL_DATA_A {
    cbSize: mem::size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>().try_into().unwrap(),
    DevicePath: [0],
});

Now call SetupDiGetDeviceInterfaceDetailA again to actually obtain the data you require:

let ok = unsafe {
    SetupDiGetDeviceInterfaceDetailA(
        device_info_set,
        device_iface_data,
        device_iface_detail.as_mut_ptr(),
        required_size,
        ptr::null_mut(),
        ptr::null_mut() // XXX: or actually put a pointer to a structure here
    )
}

Finally, extract the device path from the buffer:

if ok {
    let path = unsafe {
        std::ffi::CStr::from_ptr(
            ptr::addr_of!(device_iface_detail.assume_init_ref().DevicePath)
            as *const std::os::raw::c_char
        )
    };
    
    /* ... use path and devinfo_data.assume_init_mut() ... */
}

The path will be usable as long as buf stays in scope. If you want to keep it for longer, you will need to copy it into a CString or some other owning container.

like image 118
user3840170 Avatar answered May 10 '26 07:05

user3840170



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!