Reputation: 170410
There are several ways to list serial ports under Windows but I'm not sure what is the proper way: the way that does detect all serial ports that are available.
One good code example is http://www.naughter.com/enumser.html - where there are 9 (nine!) ways of enumerating serial devices.
The question is: what is the optimal way of doing it.
Requirements:
COMx
.Upvotes: 11
Views: 55523
Reputation: 61
The right way to enumerate COM ports is using SetupDi functions. Trying to open COM ports using CreateFile() is too slow for up to 256 port names, and will skip over already-open ones.
For Windows, all serial ports have an alias matching to "COM%u". Sure! The necessary set of SetupDi functions is available since Windows 98, so your software can keep a quite high backward-compatibility level. Only when DOS or Windows < 98 is required, enumerate-by-opening is OK and fast as such systems only support up to COM4.
An example for filling a ComboBoxEx here:
#include <windowsx.h>
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h> //CM_Get_Parent
// Fills or re-fills (after WM_DEVICECHANGE) a ComboBoxEx with all COM ports
// and their descriptive names.
// <nr> is the zero-based current (i.e. to be selected) COM port number.
void FillComboComPorts(HWND hCombo,UINT nr) {
ComboBox_ResetContent(hCombo);
HANDLE devs=SetupDiGetClassDevs((LPGUID)&GUID_DEVCLASS_PORTS,0,0,DIGCF_PRESENT);
if (devs==INVALID_HANDLE_VALUE) return;
SP_DEVINFO_DATA devInfo;
devInfo.cbSize=sizeof devInfo;
TCHAR s[80];
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_IMAGE|CBEIF_LPARAM|CBEIF_OVERLAY|CBEIF_SELECTEDIMAGE|CBEIF_TEXT;
cbei.pszText=s;
for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
HKEY hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
if (hKey==INVALID_HANDLE_VALUE) continue;
TCHAR t[16]; // The COM port name will be placed here
*t=0;
DWORD len=sizeof(t);
RegQueryValueEx(hKey,T("PortName"),0,0,(LPBYTE)t,&len);
RegCloseKey(hKey);
if (*t!='C') continue; // bail out on errors and LPT%u
cbei.lParam=StrToInt(t+3)-1; // I use zero-based numbering
// Already open COM ports are marked with an overlay
// If your <nr> is currently open by design, change code here.
HANDLE h=myOpen((UINT)cbei.lParam);
if (h) CloseHandle(h);
cbei.iOverlay=h?0:2; // 2 is the "not available" overlay.
DEVINST parent; // Graying text would require ownerdrawn combobox and much more code
const GUID*pguid=&GUID_DEVCLASS_PORTS;
// Show class icon for parent(!) device class, so get a clue where your COM port is connected (USB or onboard, or somewhere else)
if (!CM_Get_Parent(&parent,devInfo.DevInst,0)) {
ULONG l=sizeof s;
if (!CM_Get_DevNode_Registry_Property(parent,CM_DRP_CLASSGUID,0,s,&l,0)) {
GUID guid;
if (!CLSIDFromString(s,&guid)) pguid=&guid; // change pointer on success
}
}
SetupDiGetClassImageIndex(&ild,pguid,&cbei.iImage);
cbei.iSelectedImage=cbei.iImage;
// Show descriptive string, not only COM%u
SetupDiGetDeviceRegistryProperty(devs,&devInfo,SPDRP_FRIENDLYNAME,0,(PBYTE)s,sizeof s,0); // ab Windows 2k?
// The COM number should bei included by help of device driver
// If not, append it
if (!StrStr(s,t)) { // Caution! StrStr(Haystack,Needle) vs. strstr(needle,haystack)
int l=lstrlen(s);
wnsprintf(s+l,elemof(s)-l,T(" (%s)"),t);
}
cbei.iItem=myFindIndex(hCombo,cbei.lParam); // helper for sorting by COM number
SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
if (UINT(cbei.lParam)!=nr) continue;
ComboBox_SetCurSel(hCombo,cbei.iItem);
}
SetupDiDestroyDeviceInfoList(devs);
}
// Locates upcoming <t> in sorted list by ItemData, convergates fast
static int myFindIndex(HWND hCombo, LPARAM t) {
int l=0, r=ComboBox_GetCount(hCombo);
while(l!=r) {
int m=(l+r)>>1;
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_LPARAM;
cbei.iItem=m;
SendMessage(hCombo,CBEM_GETITEM,0,(LPARAM)&cbei);
if (cbei.lParam>t) r=m;
else l=m+1;
}
return l;
}
// My personal zero-based COM port opener; returns 0 on failure
static HANDLE myOpen(UINT ComNr,DWORD dwFlags=0) {
TCHAR s[12];
wnsprintf(s,elemof(s),T("\\\\.\\COM%u"),ComNr+1);
HANDLE h=CreateFile(s,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,dwFlags,0);
if (h==INVALID_HANDLE_VALUE) h=0;
return h;
}
// The ComboBoxEx needs such an ImageList
static SP_CLASSIMAGELIST_DATA ild;
// initialization somewhere before
ild.cbSize=sizeof ild;
SetupDiGetClassImageList(&ild);
SendMessage(hCombo,CBEM_SETIMAGELIST,0,(LPARAM)ild.ImageList);
// and SetupDiDestroyClassImageList(&ild) when done
Upvotes: 1
Reputation: 383
Modified @Dženan answer to use wide characters and returning list of ints
#include <string>
#include <list>
list<int> getAvailablePorts()
{
wchar_t lpTargetPath[5000]; // buffer to store the path of the COM PORTS
list<int> portList;
for (int i = 0; i < 255; i++) // checking ports from COM0 to COM255
{
wstring str = L"COM" + to_wstring(i); // converting to COM0, COM1, COM2
DWORD res = QueryDosDevice(str.c_str(), lpTargetPath, 5000);
// Test the return value and error if any
if (res != 0) //QueryDosDevice returns zero if it didn't find an object
{
portList.push_back(i);
//std::cout << str << ": " << lpTargetPath << std::endl;
}
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
}
}
return portList;
}
Upvotes: 6
Reputation: 518
You can check the windows registry base to list all COM ports. Here is my code > github file
Upvotes: 0
Reputation: 3395
This is a modernized version of @michael-jacob-mathew's answer:
#include <iostream>
#include <string>
#include <Windows.h>
bool SelectComPort() //added function to find the present serial
{
char lpTargetPath[5000]; // buffer to store the path of the COMPORTS
bool gotPort = false; // in case the port is not found
for (int i = 0; i < 255; i++) // checking ports from COM0 to COM255
{
std::string str = "COM" + std::to_string(i); // converting to COM0, COM1, COM2
DWORD test = QueryDosDevice(str.c_str(), lpTargetPath, 5000);
// Test the return value and error if any
if (test != 0) //QueryDosDevice returns zero if it didn't find an object
{
std::cout << str << ": " << lpTargetPath << std::endl;
gotPort = true;
}
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
}
}
return gotPort;
}
It produces the following output on my computer:
COM1: \Device\Serial0
COM3: \Device\VCP0
Upvotes: 4
Reputation: 1
CUIntArray ports;
EnumerateSerialPorts(ports);
for (int i = 0; i<ports.GetSize(); i++)
{
CString str;
str.Format(_T("COM%d"), ports.ElementAt(i));
m_ctlPort.AddString(str);
}
Upvotes: -3
Reputation: 358
If you can access the registry, the HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
key contains a list of COM ports Windows currently supports (in some cases, this information may be stale/incorrect; like, I suspect, when a plug & play device providing serial ports has not completed detection/installation or has been recently removed).
This is the way .NET Framework's SerialPort.GetPortNames()
method reports available COM ports, and the above information is derived from the linked page.
Upvotes: 5
Reputation: 185
void SelectComPort() //added function to find the present serial
{
TCHAR lpTargetPath[5000]; // buffer to store the path of the COMPORTS
DWORD test;
bool gotPort=0; // in case the port is not found
for(int i=0; i<255; i++) // checking ports from COM0 to COM255
{
CString str;
str.Format(_T("%d"),i);
CString ComName=CString("COM") + CString(str); // converting to COM0, COM1, COM2
test = QueryDosDevice(ComName, (LPSTR)lpTargetPath, 5000);
// Test the return value and error if any
if(test!=0) //QueryDosDevice returns zero if it didn't find an object
{
m_MyPort.AddString((CString)ComName); // add to the ComboBox
gotPort=1; // found port
}
if(::GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
lpTargetPath[10000]; // in case the buffer got filled, increase size of the buffer.
continue;
}
}
if(!gotPort) // if not port
m_MyPort.AddString((CString)"No Active Ports Found"); // to display error message incase no ports found
}
Upvotes: 9
Reputation: 941397
Serial ports are very simple devices, dating from the stone age of computing hardware. They don't support Plug & Play, there is no way to tell that somebody plugged in a device. The only thing you can do is discover what ports are available, the SerialPort.GetPortNames() returns the list. Some USB emulators can generate a descriptive name to go with the port name, you can discover those with WMI, Win32_SerialPort class.
None of which helps you discover what COM port is connected to a particular device. Only a human knows, she physically plugged the cable in the connector. You'll need to provide a config UI that lets the user select the port number. A combo box gets the job done. Save the selection in your config data, it is very likely that the device is still connected to the same port the next time your program starts.
Upvotes: 3