即用型串行端口枚举列表框






4.97/5 (19投票s)
使用 Setup API 枚举串行端口,以便在列表框和下拉组合框中使用。
引言
关于串口枚举还有其他文章。但大多数文章都使用某种注册表解析来进行枚举,并且需要额外的工作来提供用于选择的对话框控件。本文为您提供现成的控件,可用于配置对话框等。
背景
串口枚举方法概述
方法 | 备注 |
---|---|
Setup API | Win95 和 NT4 不可用 |
注册表 | 不同 Windows 版本的文件位置不同 |
打开 | 尝试打开所有可能的设备;对已打开的设备无效 |
GetDefaultCommConfig | 仅适用于 COM1 到 COM9;执行缓慢 |
Windows Setup API 包含枚举包括 COM 端口在内的已安装硬件的所有必需函数。该 API 通过读取注册表来检索信息。但使用此 API,我们无需担心注册表和不同 Windows 版本 COM 端口的条目。此外,我们还可以获得 Windows 显示的用户友好名称(例如,在硬件控制面板中),并区分物理端口和虚拟端口。
有两篇文章使用 Setup
API 描述了串口枚举,可能对您有帮助:
- Serial ports, Enumeration and FIFO control by Vladimir Afanasyev.
- Enumerating serial ports - W2K style by Zach Gorman.
Using the Code
CComPortList
和 CComPortCombo
类提供了现成的 CListBox
和 CComboBox
派生控件,用于串口选择。要使用它们,请创建一个带有单个选择列表框或下拉列表组合框的对话框,为控件添加一个成员 var
,在对话框的头文件中包含 CComPortList
或 CComPortCombo
头文件,并将控件类型从 CListBox/CComboBox
更改为 CComPortList/CComPortCombo
。
控件初始化
然后在对话框的 OnInitDialog
中初始化控件。
有四个可选函数可以控制包含在列表中的端口类型。在通过枚举填充列表之前应调用它们。
// 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)
会在列表顶部添加一个 "<无>
" 项,表示未选择任何端口。此选项默认设置。这对于如今不再拥有串行端口的现代系统特别有用,可以清晰地指示这一点,而不是显示一个空的控件。使用 SetNoneStr(LPCTSTR s)
显示 "<无>
" 以外的 string
。
SetOnlyPhysical(BOOL bSet)
将仅把物理 COM 端口添加到列表中,忽略虚拟端口。此选项默认情况下是清除的。
SetOnlyPresent(BOOL bSet)
将仅把存在的 COM 端口添加到列表中,忽略过去出现过的端口。此选项默认情况下是设置的。这些不存在的端口通常是过去使用但实际未插入的 USB 转 RS232 转换器等虚拟端口。但也可能是 BIOS 中已禁用的物理端口。
初始化函数接受一个可选参数,用于预选当前使用的或已配置的端口。
// 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());
可以通过传递端口号(从 1
开始)或传递给 Open
函数的文件名("COM<n>
" 或 "\\.\COM<n>
")来指定端口。当传递 -1
或端口不存在时,如果使用,将预先选择 "<无>
" 项。否则,如果列表中只有一个项,则选择该项;如果有多个项,则不选择任何项。
从控件获取选择
这通常在对话框的 OnOK
函数中完成。与初始化一样,有两种函数可以检索选定的 COM 端口。
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()
返回端口号,bool GetFileName(CString& strPort)
将文件名复制到 strPort
。
GetPortNum()
在未选择任何项时返回 -1
,在选择 "<无>
" 项时返回 0
。当未选择任何项时,GetFileName()
将 strPort
设置为空 string
并返回 false
。当选择 "<无>
" 项时,strPort
也为空,但返回 true
。对于所有其他有效选择,strPort
包含要传递给 Open
函数的文件名。对于端口号 < 10
,它是 "COM<n>
",对于端口号 ≥ 10
则是 "\\.\COM<n>
"。
要检查所选端口是物理的还是虚拟的,请使用 BOOL IsPhysicalPort()
或 BOOL IsVirtualPort()
。
CEnumDevices
类提供了 static
函数
static int GetPortFromName(LPCTSTR lpszPort);
static bool GetFileName(int nPortNum, CString& str);
从文件名获取端口号并从端口号创建文件名字符串。
控件背后
控件类 CComPortList
和 CComPortCombo
只包含少数成员变量和函数(许多是内联的)。真正的功劳(串口枚举)是由 CEnumDevices
类在调用枚举函数时完成的。
BOOL EnumSerialPorts(CObject* pList, EnumCallBack pCallBack, BOOL bPresent = TRUE);
第一个参数是指向调用控件的指针('this
'),由作为第二个参数传递的控件的静态回调包装函数使用。静态回调函数
typedef void (CALLBACK* EnumCallBack)(CObject*, const CEnumDevInfo*);
在枚举期间找到的每个端口都被调用,传递控件的指针和设备数据的指针。然后,静态回调函数调用成员函数将项添加到列表中。
/*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);
}
}
CEnumDevices
类设计用于枚举串行端口,但也可以通过函数 BOOL EnumDevices(unsigned nInfo, CObject* pList, EnumCallBack pCallBack, const GUID* lpGUID);
用于枚举其他设备。
支持的 Windows 版本
CEnumDevices
类根据必须支持的 Windows 版本使用不同的实现(如果需要,则使用动态绑定)。Windows 95 支持未经过测试,因此我不知道代码是否有效(可能会得到空的控件)。使用的 Setup
API 函数在 Windows NT4 上不起作用。但源文件包含一个回退机制,用于在启用 NT4 支持时从注册表中检索信息。
在 Windows 9x 上,Setup API 仅包含 ANSI 函数。CEnumDevices
类通过调用 ANSI 函数并将结果转换为 Unicode 来支持 Windows 9x 上的 Unicode 构建。
关注点
演示应用程序有一个设备更改处理程序,可以在虚拟 COM 端口添加到系统或从系统中移除时更新列表。当应用程序使用虚拟 COM 端口来检测插入和拔出时,此类处理程序(通常位于 CMainFrame
派生类中)非常有用。
历史
代码基于我于 2004 年至 2006 年间编写的各种来源。
- 2011 年 12 月 1 日:首次 CodeProject 发布版本