前言:
在串口通信中,为了在连接串口设备时,能够方便快捷,本人通过C++自写了一个串口自动查找的软件,其中VC++提供了两种查找方式,以下将针对两种方式做一个对比。
方式一:通过函数 CreateFile查找
参看本人前一篇文章《MFC串口号自动查找的实现》,CreateFile函数可以用来创建系统设备文件,这里用来创立串口,通过逐个串口的判断:如果该串口设备存在,则 m_hCom = INVALID_HANDLE_VALUE 成立。不存在或者被占用,则会返回一个错误,即 m_hCom ≠ INVALID_HANDLE_VALUE ,据此可以判断串口存在可用与否。详细参见MSDN中的介绍。
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL);
if (m_hCom != INVALID_HANDLE_VALUE)
{
// 如果没有该设备,或者被其他应用程序在用
}
else
{
// 如果纯在该串口
}
如此通过逐个判断就可以查找到设备上所有存在的串口,程序如下:
for (; jTmp <= iTmp; jTmp++)
{
if (jTmp < 10)
{
comStr.Format(_T("COM%d"), jTmp);
}
else
{
comStr.Format(_T("\\\\.\\COM%d"), jTmp);//大于10就要改变输入方式,否则找不到
}
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL); // 这里的CreateFile函数起了很大的作用,可以用来创建系统设备文件,如果该设备不存在或者被占用,则会返回一个错误,
//即下面的 INVALID_HANDLE_VALUE ,据此可以判断可使用性。详细参见MSDN中的介绍。
if (m_hCom != INVALID_HANDLE_VALUE) // 如果没有该设备,或者被其他应用程序在用
{
//查找可用串口
comStr.Format(_T("COM%d"), jTmp);
comPortStr += comStr; //全局记录字符串,edit用于显示
comPortStr += " ";
m_combobox1.AddString(comStr); //写入combobox
ComNum[kTmp++] = jTmp;
if (kTmp % 8 == 0)
comPortStr += "\r\n ";
}
SetCommMask(m_hCom, 0);// 关闭文件句柄,后面我们采用控件,不用API
CloseHandle(m_hCom);
}
然而,这里面存在一些问题:
1、执行时间较长。因为是逐个判断,串口搜索的时间会比较长,不过一般在执行中这个时间可以接受。
2、串口号太大就会出错。在Visual Studio的Microsoft Communications Control工具中,使用WIN32 API 打开当COM号大于10的时候,会出现打开错误或失败。为了增加对COM10及以上串行端口的支持,文件名(以COM10为例)改为:\\.\COM10。代码如下:
if (ComNum < 10)
{
comStr.Format(_T("COM%d"), ComNum );
}
else
{
comStr.Format(_T("\\\\.\\COM%d"), ComNum );//大于10就要改变输入方式,否则找不到
}
尽管如此,一般的 MSCOMM32.OCX 支持的缘故,有的设备上串口号大于16时就会报错。所以需要对现有的 MSCOMM32.OCX 进行更新。这里是更新后的 MSCOMM32.OCX 支持COM~COM255.https://download.csdn.net/download/wjy27/88333486
方式二:通过查注册表的方法。
在Windows操作系统中,串口的插拔都会在注册表上有所记录,路径为:
计算机\HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM\
所以在需要查找设备串口时,只要读取注册表的数值即可,首先通过RegOpenKeyEx函数打开指定的注册表项,获得一个返回结果,如果函数成功,返回值为ERROR_SUCCESS。 如果函数失败,返回值是Winerror.h中定义的非零错误代码。我们可以使用FormatMessage函数和FORMAT_MESSAGE_FROM_SYSTEM标志来获得错误的通用描述。最后通过RegEnumValue函数来枚举指定项的值:
SeComRegeSult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
_T("Hardware\\DeviceMap\\SerialComm"),
NULL,
KEY_READ,
&ReHkey);
if (ERROR_SUCCESS == SeComRegeSult) // 打开串口注册表
{
BYTE PortName[0x100], ComName[0x100];
DWORD dwLong, dwSize;
do
{
dwSize = sizeof(PortName) / sizeof(TCHAR);
dwLong = dwSize;
SeComRegeSult = RegEnumValue(ReHkey, i, (LPSTR)PortName, (LPDWORD) & dwLong, NULL, NULL, (LPBYTE)ComName, (LPDWORD) & dwSize);
if (ERROR_NO_MORE_ITEMS == SeComRegeSult)
{
// 枚举串口
break; // commName就是串口名字"COM4"
}
comList.push_back(ComName);
i++;
} while (1);
RegCloseKey(ReHkey);
}
这样通过都注册表里面记录的串口信息就可以很快获得当前设备所注册的串口设备,比方法一实现起来要简便快捷好多。
然而,因为这种方式太依赖注册表,它也存在一些问题:
1、查找路径固定。只能针对特定的注册表路径去查找如:\HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM\。如果所用的操作系统注册路径有所差异,则就会出现查找不到串口的问题。
2、不能识别虚拟串口。对于模拟串口,只要在注册表存在的,无论硬件设备上是否真实存在,它都不能鉴别,这样就导致系统报错。下图COM15为虚拟串口,设备管理器中是看不到的:
三、两种方式的验证:
通过运行程序来进行验证,搜到的串口:
验证程序链接:https://download.csdn.net/download/wjy27/88333820
最后,通过两个程序对比我们可以看出来,方式一虽然繁琐一些,但是相对来说比较可靠,因为它不依赖于一个虚有的注册信息,而是通过实际的验证来判断串口的存在,在应对一些比较特殊的环境(例如系统注册表异常,设备中存在虚拟串口等)时,它更加稳定。