Reputation: 157
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
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.
Upvotes: 2