J Hinton
J Hinton

Reputation: 157

Cannot detect serial ports available under Windows 10 with winAPI tools

My program normally detects what Serial Ports are available from the OS at start up. It is a simple method of polling if a port can be accessed by name.

project defines for serial port

std::string COMPortNumber[MAXPORTS] {"\\\\.\\COM1",  "\\\\.\\COM2",  "\\\\.\\COM3",  "\\\\.\\COM4",  "\\\\.\\COM5",
                                 "\\\\.\\COM6",  "\\\\.\\COM7",  "\\\\.\\COM8",  "\\\\.\\COM9",  "\\\\.\\COM10",
                                 "\\\\.\\COM11", "\\\\.\\COM12", "\\\\.\\COM13", "\\\\.\\COM14", "\\\\.\\COM15",
                                 "\\\\.\\COM16", "\\\\.\\COM17", "\\\\.\\COM18", "\\\\.\\COM19", "\\\\.\\COM20"};

std::string COMPortName[MAXPORTS] = {"com1",  "com2",  "com3",  "com4",  "com5",  "com6",  "com7",  "com8",  "com9", "com10",
                                "com11", "com12", "com13", "com14", "com15", "com16", "com17", "com18", "com19", "com20"};

polling function:

void updateSerialList(){
ComboBox_ResetContent(SerialPortDropDown); //clears all content from drop down box
//int iresult = ComboBox_AddString(SerialPortDropDown, "Update Port List\0");
for(int n=0; n<MAXPORTS; n++)
{
    COMPortAvailable[n] = serial.getComPortList( COMPortNumber[n] );
    if(COMPortAvailable[n] == true)
    {
        char* tempBuf = new char[COMPortName[n].length() + 1];
        for(unsigned int t=0; t<COMPortName[n].length(); t++)
        {
            tempBuf[t] = COMPortName[n][t];
        }
        tempBuf[COMPortName[n].length()] = '\0';
        int iResult = ComboBox_AddString(SerialPortDropDown, tempBuf);
        {
            if(iResult == CB_ERR){std::cout << "error adding string" << std::endl;}
            else if(iResult == CB_ERRSPACE){std::cout << "error no room" << std::endl;}
        }
        delete[] tempBuf;
    }
}
//place baud rates in select box
for(int n=NUMBERBAUDRATES-1; n>-1; n--)
{
    char* tempBuf = new char[BaudRateName[n].length() + 1];
    for(unsigned int t=0; t<BaudRateName[n].length(); t++)
    {
        tempBuf[t] = BaudRateName[n][t];
    }
    tempBuf[BaudRateName[n].length()] = '\0';
    int iResult = ComboBox_AddString(BaudRateDropDown, tempBuf);
    {
        if(iResult == CB_ERR){std::cout << "error adding string" << std::endl;}
        else if(iResult == CB_ERRSPACE){std::cout << "error no room" << std::endl;}
    }
    delete[] tempBuf;
}

This compiles a list in a dropdown box for the user to select. It uses a function in a class for a serial instance. This is the function call inside the class.

bool getComPortList(std::string portName)
{
    bool test;
    HANDLE testSerial;
    testSerial = CreateFile(   (portName.c_str())  , GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, NULL, NULL);
    if(testSerial == INVALID_HANDLE_VALUE)
    {
        test = false;
    }
    else
    {
        test = true;
        cout << "port number " << portName << " is available" << endl;
    }
    CloseHandle(testSerial);
    return test;
}

This method has worked fine until I tried running the program on Windows 10. It previously was tested and used on Vista, Win7, Win 8.1 however even if the Windows10 device manager says comm ports are available on the system, my program cannot get a list of them.

What is different about Win10 serial port access?

Upvotes: 0

Views: 1782

Answers (1)

RbMm
RbMm

Reputation: 33774

your main logic error, that you assume - if CreateFile for some name return INVALID_HANDLE_VALUE - this mean that this name not exist. but this is of course false, because CreateFile can fail by different reasons. you need call GetLastError after fail. only if it return ERROR_FILE_NOT_FOUND the name really not exist (ERROR_PATH_NOT_FOUND can not be for "\\\\.\\COMX" because path here always exist and correct). for com devices very common error was STATUS_ACCESS_DENIED - because it have DO_EXCLUSIVE flag. with this flag only one file on device can be open at a time.

however for enumerate com devices - you need enumerate interfaces for GUID_DEVINTERFACE_COMPORT via CM_Get_Device_Interface_ListW

enumInterfaces(const_cast<PGUID>(&GUID_DEVINTERFACE_COMPORT));

static volatile UCHAR guz;

void enumInterfaces(PGUID InterfaceClassGuid)
{
    CONFIGRET status;
    ULONG len = 0, cb = 0, rcb;
    PVOID stack = alloca(guz);
    PWSTR buf = 0;

    do 
    {
        if (status = CM_Get_Device_Interface_List_SizeW(&len, InterfaceClassGuid, 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
        {
            break;
        }

        if (cb < (rcb = len * sizeof(WCHAR)))
        {
            len = (cb = RtlPointerToOffset(buf = (PWSTR)alloca(rcb - cb), stack)) / sizeof(WCHAR);
        }

        status = CM_Get_Device_Interface_ListW(InterfaceClassGuid, 0, buf, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

        if (status == CR_SUCCESS)
        {
            while (*buf)
            {
                DbgPrint("use this name in CreateFile = %S\n", buf);

                PrintFriendlyNameByInterface(buf);

                buf += 1 + wcslen(buf);
            }
        }

    } while (status == CR_BUFFER_SMALL);
}

CONFIGRET PrintFriendlyNameByInterface(PCWSTR pszDeviceInterface)
{
    ULONG cb = 0, rcb = 64;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET status;

    union {
        PVOID pv;
        PWSTR DeviceID;
        PBYTE pb;
    };

    do 
    {
        if (cb < rcb)
        {
            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
        }

        status = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0);

        if (status == CR_SUCCESS)
        {
            if (PropertyType == DEVPROP_TYPE_STRING)
            {
                DbgPrint("DeviceID = %S\n", DeviceID);

                status = PrintFriendlyNameByDeviceID(DeviceID);
            }
            else
            {
                status = CR_WRONG_TYPE;
            }

            break;
        }

    } while (status == CR_BUFFER_SMALL);

    return status;
}

CONFIGRET PrintFriendlyNameByDeviceID(PWSTR DeviceID)
{
    DEVINST dnDevInst;

    CONFIGRET status = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL);

    if (status == CR_SUCCESS)
    {
        ULONG cb = 0, rcb = 256;

        PVOID stack = alloca(guz);

        DEVPROPTYPE PropertyType;

        union {
            PVOID pv;
            PWSTR sz;
            PBYTE pb;
        };

        do 
        {
            if (cb < rcb)
            {
                rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
            }

            status = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_NAME, &PropertyType, pb, &rcb, 0);

            if (status == CR_SUCCESS)
            {
                if (PropertyType == DEVPROP_TYPE_STRING)
                {
                    DbgPrint("show this name for user = %S\n", sz);
                }
                else
                {
                    status = CR_WRONG_TYPE;
                }
            }

        } while (status == CR_BUFFER_SMALL);
    }

    return status;
}

and demo output:

use this name in CreateFile = \\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73}
DeviceID = ACPI\PNP0501\0
show this name for user = Communications Port (COM1)

in my system \\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73} is symbolic link to PDO device \Device\00000034 (created by aspi.sys) and it have not DO_EXCLUSIVE flag. despite this on second call of CreateFile i got access denied error. to this device FDO - \Device\Serial0 (\\?\COM1 symbolic link to it) attached. it already have DO_EXCLUSIVE flag. anyway SerialCreateOpen (IRP_MJ_CREATE procedure serial.sys) denied access create more than one file - at very begin in increment some counter in device extension, and if it != 1 - return STATUS_ACCESS_DENIED

so even if we try open PDO (\\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73}) which not exclusive device (setting the exclusive flag for the FDO has no effect here) - the create request begin execute on stack top from \Device\Serial0 and serial.sys enforce exclusivity themselves within their SerialCreateOpen routine.

enter image description here

Upvotes: 2

Related Questions