Search code examples
c#pinvoke

buffer size is incorrect when marshaling to native code


I'm trying to make some calls into the SetupApi. I'm specifically having problems with SetupDiGetDeviceInterfaceDetail().

Here's my definition for the native method:

class NativeMethods {
    [DllImport("SetupApi.dll", SetLastError = true)]
    [return : MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr hDevs,
                    ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
                    ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
                    uint deviceInterfaceDetailDataSize,
                    ref uint requiredSize,
                    ref SP_DEVINFO_DATA deviceInfoData);
}

Here's the native vs. managed definitions for the structures involved:

[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct GUID
{                    
    public Int32 Data1;
    public Int16 Data2;
    public Int16 Data3;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] Data4;

    public GUID(Int32 d1, Int16 d2, Int16 d3, byte[] d4)
    {
        Data1 = d1;
        Data2 = d2;
        Data3 = d3;
        Data4 = new byte[8];
        Array.Copy(d4, Data4, d4.Length);
    }
}

typedef struct _SP_DEVINFO_DATA {
    DWORD cbSize;
    GUID  ClassGuid;
    DWORD DevInst;    // DEVINST handle
    ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;

[StructLayout(LayoutKind.Sequential)]
struct SP_DEVINFO_DATA
{
    public uint cbSize;
    public GUID  ClassGuid;
    public uint DevInst;
    public UIntPtr Reserved;
}

typedef struct _SP_DEVICE_INTERFACE_DATA {
    DWORD cbSize;
    GUID  InterfaceClassGuid;
    DWORD Flags;
    ULONG_PTR Reserved;
} SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA;

[StructLayout(LayoutKind.Sequential)]
struct SP_DEVICE_INTERFACE_DATA
{
    public uint cbSize;
    public GUID  InterfaceClassGuid;
    public uint Flags;
    public UIntPtr Reserved;
}

typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA_A {
    DWORD  cbSize;
    CHAR   DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA_A, *PSP_DEVICE_INTERFACE_DETAIL_DATA_A;

[StructLayout(LayoutKind.Sequential)]
struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
    public uint cbSize;
    public IntPtr devicePath;
}

For that last structure, I read a previous post here that says structures, defined as SP_DEVICE_INTERFACE_DETAIL_DATA is with a variable length array, the C# structure should use an IntPtr. To that end, I'm allocating memory through Marshal.AllocHGlobal() as is demonstrated below. Here's how I'm using the SetupDiGetDeviceInterfaceDetail():

uint requiredSize = 0; // is ignored in this usage
byte[] foo = new byte[1024];

SP_DEVINFO_DATA spDevInfoData = new SP_DEVINFO_DATA();
spDevInfoData.cbSize = (uint)Marshal.SizeOf(spDevInfoData);

SP_DEVICE_INTERFACE_DATA spDevInfData = new SP_DEVICE_INTERFACE_DATA();
spDevInfData.cbSize = (uint)Marshal.SizeOf(spDevInfData);

SP_DEVICE_INTERFACE_DETAIL_DATA spDevInfDetailData = new SP_DEVICE_INTERFACE_DETAIL_DATA();
spDevInfDetailData.cbSize = 5; // the size needs to be 5
spDevInfDetailData.devicePath = Marshal.AllocHGlobal(foo.Length); 

NativeMethods.SetupDiGetDeviceInterfaceDetail(devList,
                                              ref spDevInfData,
                                              ref spDevInfDetailData, 
                                              spDevInfDetailData.cbSize,
                                              ref requiredSize,
                                              ref spDevInfoData);

I'm using SetupDiGetClassDevs() and SetupDiEnumDeviceInterfaces() prior to my usage of SetupDiGetDeviceInterfaceDetail(). In both of those instances, the methods work. I'm provided the list of devices from the first function, and am iterating over the list with the enum call.

However, when I call SetupDiGetDeviceInterfaceDetail(), the function fails and the Win32 GetLastError() returns an error of 122 which I found is: ERROR_INSUFFICIENT_BUFFER. I'm not seeing why my buffer is of insufficient size. I'm basically doing what I'm doing in the C++ "test app" because I couldn't get this working in C#. In that app, I'm using a char[] array 1024 members that is assigned to the SP_DEVICE_INTERFACE_DETAIL_DATA structure. That's why I'm using a byte array 1024 members in C#.

Any help or insights are greatly appreciated.


Solution

  • Yes, you are not using the function correctly. SP_DEVICE_INTERFACE_DETAIL_DATA is a variable sized structure, ANYSIZE_ARRAY has no meaning. You have to call the function twice. In the first call pass IntPtr.Zero for DeviceInterfaceDetailData and 0 for DeviceInterfaceDetailDataSize. The returned RequiredSize tells you how much memory to allocate for the structure. Allocate and call again, now passing the pointer and the size.