There are other articles about serial port enumeration. But most of them use some kind of Registry parsing for enumeration and require additional work to provide dialog controls for selection. This article gives you ready-to-use controls for usage in configuration dialogs etc.
Overview of methods to enumerate serial ports:
||Not usable with Win95 and NT4
||Different locations with different Windows versions
||Brute force trying to open all possible devices; fails on opened devices
|Works only for COM1 to COM9; slow execution
The Windows Setup API contains all the necessary functions to enumerate installed hardware including COM ports. The API retrieves information by reading the Registry. But by using this API, we don't need to bother about the Registry and the entries for COM ports with different Windows versions. Additionally, we get the user friendly name as shown by Windows (e.g., in the hardware control panel) and can differentiate between physical and virtual ports.
There are two articles describing serial port enumeration using the Setup API which may be of interest:
Using the code
CComPortCombo classes provide ready-to-use
CComboBox derived controls for serial port selection. To use them, create a dialog with a single selection list box or a dropdown-list combo box, add a member var for the control, include the
CComPortCombo header file in the dialogs header file, and change the control type from
Then initialize the control from within
OnInitDialog of the dialog.
There are four optional functions that control the kind of ports included in the list. They should be called before filling the list by enumeration:
SetNoneItem(BOOL bSet) will add a "<None>" item on top of the list indicating that no port is selected. This option is set by default. This is especially useful with modern systems that do not have serial ports anymore to indicate this clearly rather than showing an empty control. Use
SetNoneStr(LPCTSTR s) to show another string than "<None>".
SetOnlyPhysical(BOOL bSet) will add only physical COM ports to the list ignoring virtual ports. This option is cleared by default.
SetOnlyPresent(BOOL bSet) will add only present COM ports to the list ignoring ports that has been seen in the past. This option is set by default. Such non-present ports are usually virtual ports like USB-to-RS232 converters that has been used in the past but are actually not plugged in. But it may also be physical ports that have been disabled in the BIOS.
The initialization functions accept one optional parameter to pre-select the actually used or configured port:
int nPortNum = AfxGetApp()->GetProfileInt(_T("Config"), _T("ComPort"), -1);
The port may be specified by passing its number (starting at 1) or the file name as passed to the
Open functions ("COM<n>" or "\\.\COM<n>"). When passing -1 or the port does not exist, the "<None>" item will be pre-selected if used. Otherwise, the only item is selected if the list contains only one item and no item is selected if there are multiple items.
Getting the selection from the control
This is usually performed in the
OnOK function of the dialog. Like for initialization, there are two functions to retrieve the selected COM port:
int nPortNum = m_listPorts.GetPortNum();
if (m_listPorts.GetCount() && nListPortNum <= 0)
AfxMessageBox(_T("No COM port has been selected."));
AfxGetApp()->WriteProfileInt(_T("Config"), _T("ComPort"), nPortNum);
int GetPortNum() returns the port number, and
bool GetFileName(CString& strPort) copies the file name to
GetPortNum() returns -1 when no item has been selected and 0 when the "<None>" item is selected.
strPort to an empty string and returns false when no item has been selected. When the "<None>" item is selected,
strPort is also empty but true is returned. For all other valid selections,
strPort contains the file name to be passed to the
Open functions. For port numbers < 10, this is "COM<n>", and "\\.\COM<n>" for port numbers ≥ 10.
To check whether the selected port is physical or virtual, use
BOOL IsPhysicalPort() or
CEnumDevices class provides the static functions:
static int GetPortFromName(LPCTSTR lpszPort);
static bool GetFileName(int nPortNum, CString& str);
to get the port number from the file name and create the file name string from the port number.
Behind the controls
The control classes
CComPortCombo contain only a few member variables and functions (many are inline). The real work (enumeration of the serial ports) is performed by the
CEnumDevices class when calling the enumeration function.
BOOL EnumSerialPorts(CObject* pList, EnumCallBack pCallBack, BOOL bPresent = TRUE);
The first parameter is a pointer to the calling control ('
this') used by the static callback wrapper function of the control passed as the second parameter. The static callback function:
typedef void (CALLBACK* EnumCallBack)(CObject*, const CEnumDevInfo*);
is called for each port found during enumeration, passing the pointer to the control and a pointer to the device data. The static callback function then calls the member function to add an item to the list:
void CComPortList::CallbackWrapper(CObject* pObject, const CEnumDevInfo* pInfo)
ASSERT(pObject != NULL);
CComPortList* pThis = reinterpret_cast<CComPortList*>(pObject);
void CComPortList::AddItem(const CEnumDevInfo* pInfo)
ASSERT(pInfo != NULL);
if (pInfo->m_nPortNum > 0 &&
(!m_bOnlyPhysical || !(pInfo->m_nPortNum & DATA_VIRTUAL_MASK)))
int nItem = AddString(pInfo->m_strName.GetString());
if ((pInfo->m_nPortNum & DATA_PORT_MASK) == m_nDefPort)
CEnumDevices class has been designed to enumerate serial ports, but can be also used to enumerate other devices using the function
BOOL EnumDevices(unsigned nInfo, CObject* pList, EnumCallBack pCallBack, const GUID* lpGUID);.
Supported Windows versions
CEnumDevices class uses different implementations according to the Windows versions that must be supported (uses dynamic binding if necessary). Windows 95 support is not tested and therefore I don't know if the code works (may get empty controls). The used Setup API functions did not work with Windows NT4. But the source includes a fallback to retrieve information from the Registry when NT4 support is enabled.
With Windows 9x, the Setup API contains only ANSI functions. The
CEnumDevices class supports Unicode builds with Windows 9x by calling the ANSI functions and converting the results to Unicode.
Points of interest
The demo app has a device change handler that updates the lists when virtual COM ports are added to or removed from the system. Such a handler (usually located in the
CMainFrame derived class) is useful when the app uses a virtual COM port to detect plug-in and -out.
The code is based on various sources written by me between 2004 and 2006.
- 2011-12-01: Initial CodeProject release version.