Search code examples
c#usbioctlsetupapi

Getting the USB Device Descriptor in C# without libusb?


I want to read the Device Descriptor of a USB Serial port (not just the VID/PID -- mainly so I can get the bcdDevice, iProduct, and iManufacturer) so I can know if the USB serial port is capable of what I want it to do (we have a couple of modems in the field, and one of them is much more reliable at half-duplex).

I don't want to use libusbdotnet because I don't want my users to have to figure out ahead of time what to filter for to get the ports I'm interested in.

I see how to do it with C; looking on the USBView application, we're going to need to use the SetupAPI to enumerate the controllers (SetupDiEnumDeviceInfo), then the hubs, and then query the hubs with IOCTL to get what is on each port.

I think I have to do this this way because I can't figure out a mapping from COM13 -> \.\USB#ROOT_HUB30#4&2a9d917a&0&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}.. is there a better/easier way to get the filename I need for the ioctl, rather than go through them all and filter them?

I've also tried making the usbview application into a C DLL and tried to make a wrapper around that, but I can't get my homegrown DLL to compile properly...

I tried adding this to the enum.c from usbview just as a starting point

typedef struct
{
    PSTR Name;
    PSTR path;
    int port;
} USBDevice;

USBDevice USBDevices[255] = { 0 };
int ndevices;

    extern __declspec(dllexport) int GetDevices(PSTR filter)
    {
        //Enumerate it all and filter for "COM%"
        //Store the data in the USBDevice array.
    }

    extern __declspec(dllexport) PSTR DeviceName(int i)
    {
        return USBDevices[i].Name;
    }

    extern __declspec(dllexport) PSTR DevicePath(int i)
    {
        return USBDevices[i].path;
    }

    extern __declspec(dllexport) void FreeList(void)
    {
        char* foo;
        int i;

        for (i = 0; i<ndevices; i++)
        {
            free(USBDevices[i].Name);
            free(USBDevices[i].path);
        }

        ndevices = 0;
    }

    extern __declspec(dllexport) PUSB_COMMON_DESCRIPTOR GetDevuceInfo(PSTR path, int port)
    {
        //I didn't want to figure out how to marshal this.
    }

    extern __declspec(dllexport) PSTR GetDevuceInfoValue(PSTR path,int port, int selector)
    {
        //Open Hub, send IOCTL to it, get data, filter that, return the interesting bit.
    }

But I can't load the resulting DLL into my C# project (it crashes when run - even before the window appears). Looking at the DLL, it appears that it is garbage (walking the DLL causes the walker to crash). so... I'm not sure what I'm doing wrong there, so I was going to translate the enum.c into C# pinvoke-style and roll with that, but it seems like a lot of work, especially if there's a simpler way to get the COM14 -> \.\USB#ROOT_HUB30#4&2a9d917a&0&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8} [Port1] link...

So... Is there a lot of work ahead of me, or is there a simpler way to get the linkage (I've tried from the registry, but it doesn't seem to have the mapping).


Solution

  • It looks like that is the approved way (EnumerateDevices, CM_GetParent, Munge it to a valid path, then ask the Hub to get the data). I've made a C program to do the dirty work and it works like a charm. I think I'll wrap it in a DLL and post it to CodeProject.

    Warning though... even if you do a DeviceIOControl, windows does not re-query your device. It only queries your device at Plug-In.

    But at least you can find the "Manufacturer String" so you can tell your USB port from all the similar ones that are plugged in. Then again, you can get that from SetupAPI, so... I don't know what it really buys you.