In this article, you will find ready-to-use controls that can be used in configuration dialogs, etc.
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:
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);
m_listPorts.InitList(nPortNum);
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."));
return;
}
AfxGetApp()->WriteProfileInt(_T("Config"), _T("ComPort"), nPortNum);
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:
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.
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);
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