Properly declare SP_DEVICE_INTERFACE_DETAIL_DATA for PInvoke

c#, memory-alignment, pinvoke, winapi

Solution

The key point of the structure is that you don't know how large it should be. You have to call SetupDiGetDeviceInterfaceDetail() twice, on the first call you intentionally pass 0 for the DeviceInterfaceDetailSize argument. That will of course fail, but the RequiredSize argument will tell you how big the structure needs to be. Then you allocate a structure of the right size and call it again.

Dynamically sizing a structure is not directly supported by the pinvoke marshaller or the C# language. So declaring the structure isn't going to help at all, don't try. You should use Marshal.AllocHGlobal(). That gets you a pointer that you can pass as the DeviceInterfaceDetailData argument. Set cbSize with Marshal.WriteInt32. Now make the call. And retrieve the returned string with Marshal.PtrToStringUni(). Marshal.FreeHGlobal to clean up. You shouldn't have any trouble googling code that does this from the method names.

The cbSize member is a problem, the SetupApi.h SDK header file contains this:

#ifdef _WIN64
#include <pshpack8.h>   // Assume 8-byte (64-bit) packing throughout
#else
#include <pshpack1.h>   // Assume byte packing throughout (32-bit processor)
#endif

That's fugly, a C compiler will think there is 2 bytes of padding after the array, even though there is not. In C# code the StructLayoutAttribute.Pack value needs to be different for 32-bit code vs 64-bit code. There is no way to cleanly do this without declaring two structures. And choose between them based on the value of IntPtr.Size. Or just hard-code it since the structure declaration isn't useful anyway, it is 6 in 32-bit mode and 8 in 64-bit mode. The string starts at offset 4 in both cases. Assuming Unicode strings of course, no point in using ansi strings.

Problem

The `SP_DEVICE_INTERFACE_DETAIL_DATA` structure: ``` typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA { DWORD cbSize; TCHAR DevicePath[ANYSIZE_ARRAY]; } SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA; ``` How do I declare it in C# to get `Marshal.SizeOf` work properly? I don't have a problem with allocating dynamic buffer. I only want to calculate `cbSize` in a proper, non-hardcoded manner. The definition at PInvoke.net is wrong. The explanation at PInvoke.net is also wrong: ``` SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA(); didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // trust me :) ``` Don't trust him. `4 + Marshal.SystemDefaultCharSize` is only valid on x86. Same for `sizeof(int) + Marshal.SystemDefaultCharSize`. On x64 it fails miserably. This is what unmanaged C++ gives: x86 Struct size A: `5` Offset of device path A: `4` Struct size W: `6` Offset of device path W: `4` x64 Struct size A: `8` Offset of device path A: `4` Struct size W: `8` Offset of device path W: `4` I tried every possible combination of `StructLayout` and `MarshalAs` parameters, but I couldn't get it to return the above values. What is the correct declaration?

Original source

Related problems