Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Ready-to-Use Serial Port Enumeration List Box

4.97/5 (19 votes)
26 Mar 2021CPOL5 min read 78.2K   10.4K  
Using the Setup API to enumerate serial ports for selection with list boxes and drop-down combo boxes
In this article, you will find ready-to-use controls that can be used in configuration dialogs, etc.

Demo screenshot

Introduction

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.

Background

Overview of methods to enumerate serial ports:

Method Remarks
Setup API Not usable with Win95 and NT4
Registry Different locations with different Windows versions
Open Brute force trying to open all possible devices; fails on opened devices
GetDefaultCommConfig 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

The CComPortList and CComPortCombo classes provide ready-to-use CListBox and 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 CComPortList or CComPortCombo header file in the dialogs header file, and change the control type from CListBox/CComboBox to CComPortList/CComPortCombo.

Control Initialization

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:

C++
// By default, the first combo box item is "<None>".
//    m_listPorts.SetNoneItem(0);
// The default strings may be also change using SetNoneStr().
//    m_listPorts.SetNoneStr(_T("No port"));
// By default, all COM ports are listed.
//    m_listPorts.SetOnlyPhysical(1);
// By default, only present COM ports are listed.
//    m_listPorts.SetOnlyPresent(0);

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:

C++
// Pre-select the configured port
int nPortNum = AfxGetApp()->GetProfileInt(_T("Config"), _T("ComPort"), -1);
m_listPorts.InitList(nPortNum);
// You may also use the COM port file name "COM<n>" or "\\.\COM<n>".
//    CString strPort = AfxGetApp()->GetProfileString(_T("Config"), _T("ComPortStr"));
//    m_listPorts.InitList(strPort.GetString());

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:

C++
int nPortNum = m_listPorts.GetPortNum();
if (m_listPorts.GetCount() && nListPortNum <= 0)
{
    AfxMessageBox(_T("No COM port has been selected."));
    return;
}
AfxGetApp()->WriteProfileInt(_T("Config"), _T("ComPort"), nPortNum);
// You may also save the COM port file name "COM<n>" or "\\.\COM<n>".
//    CString strPort;
//    m_listPorts.GetFileName(strPort);
//    AfxGetApp()->WriteProfileString(_T("Config"), 
//     _T("ComPortStr"), strPort.GetString());

int GetPortNum() returns the port number, and bool GetFileName(CString& strPort) copies the file name to strPort.

GetPortNum() returns -1 when no item has been selected and 0 when the "<None>" item is selected. GetFileName() sets 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 BOOL IsVirtualPort().

The CEnumDevices class provides the static functions:

C++
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 CComPortList and 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.

C++
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:

C++
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:

C++
/*static*/ void CComPortList::CallbackWrapper(CObject* pObject, const CEnumDevInfo* pInfo)
{
    ASSERT(pObject != NULL);
    CComPortList* pThis = reinterpret_cast<CComPortList*>(pObject);
    ASSERT(pThis->IsKindOf(RUNTIME_CLASS(CComPortList)));
    pThis->AddItem(pInfo);
}

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());
        SetItemData(nItem, static_cast<DWORD>(pInfo->m_nPortNum));
        if ((pInfo->m_nPortNum & DATA_PORT_MASK) == m_nDefPort)
            SetCurSel(nItem);
    }
}

The 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

The 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.

History

The code is based on various sources written by me between 2004 and 2006.

  • 1st December, 2011: Initial CodeProject release version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)