Win32Api USB SetupDiGetDeviceInterfaceDetail fail

11,565

Solution 1

I believe I found the problem.

After poking around pinvoke.net, I found the following

// build a Device Interface Detail Data structure
 SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
 if (IntPtr.Size == 8) // for 64 bit operating systems
     didd.cbSize = 8; 
 else  didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // for 32 bit systems

Changing this in my code allows me to obtain the DevicePath properly.

Reference for those interested.

http://pinvoke.net/default.aspx/setupapi.SetupDiGetDeviceInterfaceDetail

Solution 2

Congratulations, it is working. You'll get a Unicode string, it's twice as long. And a FALSE return is correct. You just need to call Marshal.GetLastWin32Error() and verify that you got ERROR_INSUFFICIENT_BUFFER. Your C code is broken, probably because you forgot to initialize theBytesReturned to zero.

Share:
11,565
galford13x
Author by

galford13x

Updated on June 04, 2022

Comments

  • galford13x
    galford13x almost 2 years

    I am attempting to connect to a USB GPS device. I can successfully connect to the device if I manually create a file via CreateFile WinApi (using the path specified in Device Manager).

    However, when I try to select the device through enumeration I fail @ the SetupDiGetDeviceInterfaceDetail call.

    I have C code that works correctly, but my C# translation does not appear to work correctly. I have tried many variations with essentially the same results.

    C Code that works

    // Get enumerator handle for the specified ClassGuid
    HDEVINFO theDevInfo = SetupDiGetClassDevs((GUID*)&GUID_DEVINTERFACE_GRMNUSB, NULL, NULL,
        DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
    
    SP_DEVICE_INTERFACE_DATA theInterfaceData;
    theInterfaceData.cbSize = sizeof(theInterfaceData);
    
    // populate theInterfaceData which contains device class information
    if (!SetupDiEnumDeviceInterfaces(theDevInfo, NULL, (GUID*)&GUID_DEVINTERFACE_GRMNUSB, 0, &theInterfaceData) &&
        GetLastError() == ERROR_NO_MORE_ITEMS)
    {
      gHandle = 0;
      return;
    }
    // This is normally used to obtain the device path information using theInterfaceData obtained above
    bool initialized = SetupDiGetDeviceInterfaceDetail(theDevInfo, &theInterfaceData, NULL, 0, &theBytesReturned, NULL);
    // theBytesReturned = 83
    theDevDetailData =
    (PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(theBytesReturned);
    theDevDetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
    
    bool initialized = SetupDiGetDeviceInterfaceDetail(theDevInfo, &theInterfaceData, theDevDetailData, theBytesReturned, NULL, &theDevInfoData);
    

    C#

    [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern Boolean SetupDiGetDeviceInterfaceDetail(
        IntPtr hDevInfo,
        ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
        IntPtr deviceInterfaceDetailData,
        UInt32 deviceInterfaceDetailDataSize,
        out UInt32 requiredSize,
        IntPtr deviceInfoData
        );
    
    [StructLayout(LayoutKind.Sequential)]
    public struct SP_DEVICE_INTERFACE_DATA
    {
        public Int32 cbSize;
        public Guid interfaceClassGuid;
        public Int32 flags;
        private UIntPtr reserved;
    }
    
    
    // Get enumerator handle for the specified ClassGuid
    IntPtr theDevInfo = SetupDiGetClassDevs(ref ClassGuid, (DiGetClassFlags.DIGCF_PRESENT | DiGetClassFlags.DIGCF_DEVICEINTERFACE));
    
    SP_DEVICE_INTERFACE_DATA DevInterfaceData = new SP_DEVICE_INTERFACE_DATA();
    DevInterfaceData.cbSize = Marshal.SizeOf(DevInterfaceData);
    
    initialized = SetupDiEnumDeviceInterfaces(theDevInfo, IntPtr.Zero, ref ClassGuid, 0,
            ref DevInterfaceData); 
    // I assume The DevInterfaceData is populated correctly as it matches the C Code
    // And I've compared the values in memory and they match
    
    uint bytesReturned = 0;
    initialized = SetupDiGetDeviceInterfaceDetail(theDevInfo, ref DevInterfaceData, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
    // I expect bytesReturned = 83 and initialized = true which is the value that is returned in the C Code
    // Instead the value 162 is returned
    
  • user1703401
    user1703401 about 12 years
    No, that corrupts the stack. Use Marshal.AllocHGlobal() to allocate the buffer with the required size. The size you got from the first call.
  • galford13x
    galford13x about 12 years
    The C code works properly. It was the C# code that did not work properly. theBytesReturned is initialized to 0, it's just not shown in the above code as I didn't put the entire function to save space. I guess the C code returned 83 bytes due to being ASCII, but if the C# code was expecting a UNICODE string, why is it 162 and not 83x2 = 166?
  • user1703401
    user1703401 about 12 years
    I don't know, post the strings and I'll tell you why. There really is a problem with the C code, the function must return FALSE and GetLastError() must return ERROR_INSUFFICIENT_BUFFER. You then know the required buffer size, allocate it and call the function again.
  • galford13x
    galford13x about 12 years
    Oh yes I agree, the first call does return false, there is a second call that I apparently didn't put that is the actually deviceDetailData read. I'll edit my post to be correct. Here is the string @"\\?\usb#vid_091e&pid_0003#5&4a03a84&0&5#{2c9c45c2-8e7d-4c0‌​8-a12d-816bbae722c0}‌​"
  • galford13x
    galford13x about 12 years
    why/how is the stack corrupted? Wont the proper memory be marshaled when I make the Win32api call?
  • Ohad Schneider
    Ohad Schneider almost 10 years
    @HansPassant any chance you could explain why the test for IntPtr.Size is necessary and we can't rely on the marshaler to get SizeOf(SP_DEVICE_INTERFACE_DETAIL_DATA) right? I'm guessing it has to do with padding somehow?
  • user1703401
    user1703401 almost 10 years
    @Ohad - an old quirk in SetupAPI, it used 1 byte packing in the 32-bit version of Windows. Corrected to 8 byte packing in 64-bit. It can be done by the marshaller but you'd have to declare two different structs with different Pack property values.
  • Ohad Schneider
    Ohad Schneider almost 10 years
    @HansPassant Thanks for the quick turnaround! As long as I have to declare two different structs I don't see it as being much better, so I guess I'll stick with this code.