0

I'm using some legacy code to enumerate ports on my machine:

#include <windows.h>
#include <devguid.h>
#include <setupapi.h>

#include <string>
#include <iostream>
#include <assert.h>

bool GetTextProperty( std::string& sProperty, 
                      HDEVINFO dev, 
                      _SP_DEVINFO_DATA* pDeviceInfoData,
                      DWORD prop )
{
    char szBuf[MAX_PATH];
    DWORD iPropertySize = 0;
    if (SetupDiGetDeviceRegistryProperty(dev, pDeviceInfoData,
                                         prop, 0L, (PBYTE) szBuf, MAX_PATH, &iPropertySize))
    {
        sProperty = szBuf;
        assert( iPropertySize >= sProperty.size() + 1 );
        return true;
    }     
    return false;
}

inline bool readRegistryKeyValue( HKEY hKey, const std::string& key, std::string& value )
{
    bool res = false;
    CHAR szBuffer[512];
    DWORD dwBufferSize = sizeof(szBuffer);
    ULONG nError = RegQueryValueEx(hKey, key.c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize);
    if (ERROR_SUCCESS == nError)
    {
        value = szBuffer;
        res = true;
    }
    return res;
}

void ListPorts()
{
    HDEVINFO        hDevInfo;
    SP_DEVINFO_DATA DeviceInfoData;
    DWORD           i;

    hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0L, 0L, DIGCF_PRESENT);
    if ( hDevInfo == INVALID_HANDLE_VALUE )
    {
        //Medoc_ReportError(MEDOC_ERROR_HARDWARE_DRIVER_API_FAILED,
        //                  &hDevInfo, sizeof hDevInfo);
        assert( false );
    }
    else
    {
        // Enumerate through all devices in Set.
        DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
        for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData) != 0; i++)
        {
            char szBuf[MAX_PATH];
            short wImageIdx   = 0;
            short wItem       = 0;
            DWORD iPropertySize;

            if (SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceInfoData,
                                                 SPDRP_FRIENDLYNAME, 0L, (PBYTE) szBuf, MAX_PATH, &iPropertySize))
            {
                std::cout << "Smart name: " << szBuf << std::endl;

                HKEY hKey = SetupDiOpenDevRegKey(
                                hDevInfo,
                                &DeviceInfoData,
                                DICS_FLAG_GLOBAL,
                                0,
                                DIREG_DEV,
                                KEY_READ );
                if ( hKey )
                {
                    std::string portName;
                    readRegistryKeyValue( hKey, "PortName", portName );
                    std::cout << "Port name: " << szBuf << std::endl;

                    for ( DWORD prop = 0; prop != SPDRP_MAXIMUM_PROPERTY; ++prop )
                    {
                        std::string temp;
                        GetTextProperty( temp, hDevInfo, &DeviceInfoData, prop );
                        std::cout << prop << " : " << temp << std::endl;
                    }

                    RegCloseKey(hKey);
                }
            }            
        }
    }

    // Cleanup
    SetupDiDestroyDeviceInfoList(hDevInfo);
}

int main( int argc, char* argv[] )
{
    ListPorts();
    return 0;
}

Among other information, this gives me access to port name (COM*), type (FTDI for instance), VID and PID...

However, when I have many different devices based on a FTDI chip plugged, they all have the same information (SPDRP_HARDWAREID prperty reports FTDIBUS\COMPORT&VID_0403&PID_6015 or FTDIBUS\COMPORT&VID_0403&PID_6010). So I cannot discriminate who is who.

When I use a USB sniffer ("Device Monitoring Studio"), this one is able to report more relevant information withoutestablishing any connection to the ports:

enter image description here

Can this kind of extended information be accessed through Windows API to discriminate by name many devices using the same FTDI chip? Or must I use FTDI driver API to achieve this?

Ron
  • 14,674
  • 4
  • 34
  • 47
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • Does what you show in your screenshot not match the results from `SPDRP_FRIENDLYNAME`? Perhaps getting the friendly name from the parent instead of from the virtual COM port itself? But it still won't be unique, as your screenshot shows... – Ben Voigt Jun 08 '20 at 16:19
  • GUID_DEVCLASS_PORTS will get you LPT1, COM1, etc. you want to use GUID_DEVCLASS_USB if you want USB controllers. – Simon Mourier Jun 08 '20 at 17:24
  • @BenVoigt: `SPDRP_FRIENDLYNAME` gives me `USB Serial Port (COM12)` which is not so friendly ;-). I'm not looking for unique name, but just more info than "FTDI". What do you mean by "parent"? – jpo38 Jun 09 '20 at 06:15
  • @SimonMourier: When using `GUID_DEVCLASS_USB`, I get only one device (`USBXHCI`) while I have 3 connected. So this des not help. – jpo38 Jun 09 '20 at 06:17
  • Yes, because your loop quits too early when not using GUID_DEVCLASS_PORTS, I have modified your program (and also changed to unicode): https://pastebin.com/raw/UzbWS07D and it seems to work. – Simon Mourier Jun 09 '20 at 06:25
  • @SimonMourier: Thank you. This lists lots of new information I never saw before (like `Port_#0007.Hub_#0001`), but it does not contain what I'm looking for, "DX5100 OEM" or "FT230X Basic UART"....so this did not help. – jpo38 Jun 09 '20 at 07:04
  • Do you see these informations in Device Manager? – Simon Mourier Jun 09 '20 at 08:42
  • @SimonMourier: No, only in this external tool I installed ("Device Monitoring Studio") – jpo38 Jun 09 '20 at 08:55
  • @Simon Yes that information is in Device Manager, on the parent device. jpo38: In Device Manager, use "View" -> "Devices by Connection" – Ben Voigt Jun 09 '20 at 14:48
  • @BenVoigt: You are right, I can see them from here....I now understand what you meant by "parent device", this is true for thie "Devices by Connection" view...however, is there a way to retrieve this using WIN32 API? – jpo38 Jun 09 '20 at 15:03
  • Note that I can also see those information in the regular view (Devices by type), under "USB bus controllers", there's here 3 "USB Serial Converter" items, and when I go to their properties/details, some fields are populated with those "DX5100 OEM" and "FT230X Basic UART" strings....! So there must be a way to get those info using WIN32 API but how? – jpo38 Jun 09 '20 at 15:07
  • @jpo38: All the details are the same regardless of view, but "by Type" lets you see device class membership, while "by connection" allows you to see parent/child relationships. The API `CM_Get_Parent` will let you navigate from the "USB Serial Port" device (Ports class) to the "USB Serial Converter" device (USB class), and then the same `SetupDiGetDeviceRegistryProperty` lets you read anything that's in that details dropdown. – Ben Voigt Jun 09 '20 at 16:13
  • See also https://stackoverflow.com/a/56666274/103167 – Ben Voigt Jun 09 '20 at 16:14
  • There are more properties then SPDRP ones: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-properties2 Here is a sample code that dumps all properties for all USB devices: https://pastebin.com/raw/TtVFYMpT you can check if it contains the strings you're looking for. Once you have an interesting one, if you're looking for PK => name correspondance you can use this tool, ex: https://www.magnumdb.com/search?q=%7B540B947E-8B40-45BC-A8A2-6A0B894CBDA2%7D+4 – Simon Mourier Jun 10 '20 at 07:17
  • @SimonMourier: Thank you, this lists the FTDI USB Serial converter but not the "DX5100 OEM" or "FT230X Basic UART" strings. – jpo38 Jun 10 '20 at 07:26
  • @BenVoigt: Right, I could get the parent using `CM_Get_Parent`. But `SetupDiGetDeviceRegistryProperty` cannot be used anymore as it needs a `HDEVINFO` and `CM_Get_Parent` gave me a `DEVINST`. I could iterate thourgh all properties using `CM_Get_DevNode_Property_Keys`/`CM_Get_DevNode_PropertyW`, I see the good string here. But I can't find ou how to extract it: what's the right property I should use... – jpo38 Jun 10 '20 at 07:29
  • They're linked properties (the one with GUID values, for ex: DEVPKEY_Device_BaseContainerId, PKEY_Device_ContainerId, or DEVPKEY_Device_Parent, etc.) – Simon Mourier Jun 10 '20 at 07:43
  • @BenVoigt: OK, could get this info using `CM_Get_DevNode_PropertyW` with `DEVPKEY_Device_BusReportedDeviceDesc`. Do you wish to post this as an answer or should I do it (to mark this question as "answered"). – jpo38 Jun 10 '20 at 08:32
  • @SimonMourier: Yes, got it. `DEVPKEY_Device_BusReportedDeviceDesc` is the one. However, including `` leads to `C++ error LNK2001: unresolved external symbol`, any idea what new win32 .lib file should be linked to fix this? The doc does not mention any... – jpo38 Jun 10 '20 at 08:46
  • https://stackoverflow.com/questions/47714380/unresolved-external-symbol-devpkey-device-busreporteddevicedesc – Simon Mourier Jun 10 '20 at 09:20

1 Answers1

1

With the help of Ben Voigt and Simon Mourier, I could achieve this, here is the piece of code:

// More includes:
#include <initguid.h>
#include <devpkey.h>
#include <cfgmgr32.h>
// A new dependency:
#pragma comment (lib, "Cfgmgr32.lib")

bool GetDeviceProperty( const std::string& what, 
                        DEVINST dev, 
                        DEVPROPKEY prop )
{
    char szDeviceBuf[MAX_PATH];
    DEVPROPTYPE type;
    ULONG iDevicePropertySize = MAX_PATH;
    if ( CM_Get_DevNode_PropertyW(dev,
                                  &prop,
                                  &type,
                                  (PBYTE) szDeviceBuf,
                                  &iDevicePropertySize,
                                  0) == CR_SUCCESS )
    {
        wchar_t* txt = (wchar_t*) szDeviceBuf;
        std::wstring ws(txt);
        std::cout << what << " : " << std::string(ws.begin(), ws.end()) << std::endl;
        return true;
    }
    else
    {
        return false;
    }
}

void ListPorts()
{
        ...

                DEVINST devInstParent;
                auto status = CM_Get_Parent(&devInstParent, DeviceInfoData.DevInst, 0);
                if (status == CR_SUCCESS) 
                {
                    ShowDeviceProperty( "Bus reported device description", devInstParent, DEVPKEY_Device_BusReportedDeviceDesc );
                    ShowDeviceProperty( "Device description", devInstParent, DEVPKEY_Device_DeviceDesc );
                }
                else 
                {
                    continue;
                }

        ...
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • Something you also need to know is that some FTDI devices have 2 or 4 serial ports on a single chip. Then Device Manager "View -> by connection" will show a 3 layer hierarchy... the USB device, the "USB Serial Converter" function, and the Serial Port, and now the Bus Reported Device Description is on the grandparent of the Serial Port. To know how many times to call `CM_GetParent`, you can use the "Enumerator" property... it will be "USB" for the item you are looking for with a meaningful "Bus Reported Device Description", the intermediate layer will have "FTDIBUS" as the enumerator. – Ben Voigt Jun 11 '20 at 16:21
  • @BenVoigt: Thank you for your comment, that's exactly what I observe with my FTDI device, it has 2 serial ports. – jpo38 Jun 12 '20 at 07:53