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