串口编程 - windows如何枚举串口|获取活跃串口号
如需转载请标明出处
QQ技术交流群:129518033
文章目录
环境:
OS: windows 10
compiler: VS2019
前言
进行串口通信,首先会面临串口枚举的问题。本文将介绍windows如何枚举串口。
1. windows枚举设备简介
一般而言,windows下枚举的串口可以分为如下几种类型:
- 物理串口:RS232
- 虚拟串口:com0com 或 ELTIMA Virtual Serial Port 等
- 蓝牙转串口
- USB转串口
- STM32虚拟COM端口 (设备管理器显示
STMicroelectronics Virtual COM Port)(未测试)
2. windows枚举串口代码实现
以下以三种主流方式实现枚举串口,包括CreateFile函数遍历、注册表和setupapi三种方式。
| 串口 | 友好名称 | 物理设备对象名称 |
|---|---|---|
| COM1 | ELTIMA Virtual Serial Port | \Device\VSerial7_0 |
| COM2 | ELTIMA Virtual Serial Port | \Device\VSerial7_1 |
| COM4 | Prolific USB-to-Serial Comm Port | \Device\ProlificSerial0 |
| COM5 | Standard Serial over Bluetooth link | \Device\BthModem0 |
| COM6 | com0com - serial port emulator | \Device\com0com11 |
| COM7 | com0com - serial port emulator | \Device\com0com21 |
| CNCA0 | com0com - serial port emulator CNCA0 | \Device\com0com10 |
| CNCB0 | com0com - serial port emulator CNCB0 | \Device\com0com20 |
测试结果:
- COMxxx遍历
0 - COM1
1 - COM2
2 - COM4
3 - COM5
4 - COM6
5 - COM7
time : 375 ms
- 注册表
0 - CNCA0
1 - CNCB0
2 - COM1
3 - COM2
4 - COM5
5 - COM6
6 - COM7
7 - COM4
time : 0 ms
- setupapi
0 - COM6 com0com - serial port emulator
1 - COM4 Prolific USB-to-Serial Comm Port
2 - COM1 ELTIMA Virtual Serial Port
3 - CNCA0 com0com - serial port emulator CNCA0 (CNCA0)
4 - COM7 com0com - serial port emulator
5 - COM5 Standard Serial over Bluetooth link
6 - COM2 ELTIMA Virtual Serial Port
7 - CNCB0 com0com - serial port emulator CNCB0 (CNCB0)
time : 0 ms
2.1 CreateFile函数遍历
使用CreateFile函数遍历COM1-COM255,如果返回结果正确或错误为被占用,则认为是活跃串口。但是该方式只能针对串口名称为COMxxx格式的串口。
代码:
#include <iostream>
#include <string>
#include <vector>
#include "windows.h" // CreateFile GetTickCount64
#include "tchar.h" // _sntprintf _T
using namespace std;
struct SerialPortInfo
{
std::string portName;
std::string description;
};
std::string wstringToString(const std::wstring& wstr)
{
// https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
if (wstr.empty())
{
return std::string();
}
int size = WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string ret = std::string(size, 0);
WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), &ret[0], size, NULL, NULL); // CP_UTF8
return ret;
}
bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList)
{
bool bRet = false;
std::string strPortName;
HANDLE m_handle;
TCHAR portName[255] = { 0 };
SerialPortInfo m_serialPortInfo;
for (int i = 1; i <= 255; i++)
{
bRet = false;
_sntprintf(portName, sizeof(portName), _T("\\\\.\\COM%d"), i);
m_handle = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (m_handle == INVALID_HANDLE_VALUE)
{
if (ERROR_ACCESS_DENIED == GetLastError())
{
bRet = true;
}
}
else
{
bRet = true;
}
if (bRet)
{
#ifdef UNICODE
strPortName = wstringToString(portName);
#else
strPortName = std::string(portName);
#endif
m_serialPortInfo.portName = strPortName;
portInfoList.push_back(m_serialPortInfo);
}
CloseHandle(m_handle);
}
return bRet;
}
int main()
{
ULONGLONG startTick = 0;
startTick = GetTickCount64();
vector<SerialPortInfo> spInfo;
enumDetailsSerialPorts(spInfo);
for (int i = 0; i < spInfo.size(); i++)
{
std::cout << i << " - " << spInfo[i].portName << " " << spInfo[i].description << std::endl;
}
_tprintf(_T("time : %I64u ms"), GetTickCount64() - startTick);
return 0;
}
2.2 注册表
注册表HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM查询,可以实时获取活跃串口
代码:
#include <iostream>
#include <string>
#include <vector>
#include "windows.h" // CreateFile GetTickCount64
#include "tchar.h" // _sntprintf _T
using namespace std;
struct SerialPortInfo
{
std::string portName;
std::string description;
};
std::string wstringToString(const std::wstring& wstr)
{
// https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
if (wstr.empty())
{
return std::string();
}
int size = WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string ret = std::string(size, 0);
WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), &ret[0], size, NULL, NULL); // CP_UTF8
return ret;
}
bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList)
{
//https://msdn.microsoft.com/en-us/library/ms724256
#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383
HKEY hKey;
TCHAR achValue[MAX_VALUE_NAME]; // buffer for subkey name
DWORD cchValue = MAX_VALUE_NAME; // size of name string
TCHAR achClass[MAX_PATH] = _T(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys = 0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cKeyNum; // number of values for key
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
int iRet = -1;
bool bRet = false;
std::string strPortName;
SerialPortInfo m_serialPortInfo;
TCHAR strDSName[MAX_VALUE_NAME];
memset(strDSName, 0, MAX_VALUE_NAME);
DWORD nValueType = 0;
DWORD nBuffLen = 10;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hKey))
{
// Get the class name and the value count.
iRet = RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
NULL, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cKeyNum, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
if (!portInfoList.empty())
{
portInfoList.clear();
}
// Enumerate the key values.
if (cKeyNum > 0 && ERROR_SUCCESS == iRet)
{
for (int i = 0; i < (int)cKeyNum; i++)
{
cchValue = MAX_VALUE_NAME;
achValue[0] = '\0';
nBuffLen = MAX_KEY_LENGTH;//防止 ERROR_MORE_DATA 234L 错误
if (ERROR_SUCCESS == RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, (LPBYTE)strDSName, &nBuffLen))
{
#ifdef UNICODE
strPortName = wstringToString(strDSName);
#else
strPortName = std::string(strDSName);
#endif
m_serialPortInfo.portName = strPortName;
portInfoList.push_back(m_serialPortInfo);
}
}
}
else
{
}
}
if (portInfoList.empty())
{
bRet = false;
}
else
{
bRet = true;
}
RegCloseKey(hKey);
return bRet;
}
int main()
{
ULONGLONG startTick = 0;
startTick = GetTickCount64();
vector<SerialPortInfo> spInfo;
enumDetailsSerialPorts(spInfo);
for (int i = 0; i < spInfo.size(); i++)
{
std::cout << i << " - " << spInfo[i].portName << " " << spInfo[i].description << std::endl;
}
_tprintf(_T("time : %I64u ms"), GetTickCount64() - startTick);
return 0;
}
2.3 setupapi(GUID_DEVINTERFACE_COMPORT && SetupDiEnumDeviceInfo)
setupapi可以遍历设备管理器,因此可以用来枚举串口(包括友好名称、物理设备对象名称等),其本质上也是操作注册表。
代码:
// https://github.com/itas109/CSerialPort/blob/master/src/SerialPortInfoWinBase.cpp
#include <iostream>
#include <string>
#include <vector>
#include "windows.h" // CreateFile GetTickCount64
#include "tchar.h" // _sntprintf _T
#include <Setupapi.h> //SetupDiGetClassDevs Setup*
#include <initguid.h> //GUID
#pragma comment (lib, "setupapi.lib")
using namespace std;
#ifndef GUID_DEVINTERFACE_COMPORT
DEFINE_GUID(GUID_DEVINTERFACE_COMPORT, 0x86E0D1E0L, 0x8089, 0x11D0, 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73);
#endif
struct SerialPortInfo
{
std::string portName;
std::string description;
};
std::string wstringToString(const std::wstring& wstr)
{
// https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
if (wstr.empty())
{
return std::string();
}
int size = WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string ret = std::string(size, 0);
WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), &ret[0], size, NULL, NULL); // CP_UTF8
return ret;
}
bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList)
{
// https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfo
bool bRet = false;
SerialPortInfo m_serialPortInfo;
std::string strFriendlyName;
std::string strPortName;
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
// Return only devices that are currently present in a system
// The GUID_DEVINTERFACE_COMPORT device interface class is defined for COM ports. GUID
// {86E0D1E0-8089-11D0-9CE4-08003E301F73}
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (INVALID_HANDLE_VALUE != hDevInfo)
{
SP_DEVINFO_DATA devInfoData;
// The caller must set DeviceInfoData.cbSize to sizeof(SP_DEVINFO_DATA)
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); i++)
{
// get port name
TCHAR portName[256];
HKEY hDevKey = SetupDiOpenDevRegKey(hDevInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (INVALID_HANDLE_VALUE != hDevKey)
{
DWORD dwCount = 255; // DEV_NAME_MAX_LEN
RegQueryValueEx(hDevKey, _T("PortName"), NULL, NULL, (BYTE*)portName, &dwCount);
RegCloseKey(hDevKey);
}
// get friendly name
TCHAR fname[256];
SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname,
sizeof(fname), NULL);
#ifdef UNICODE
strPortName = wstringToString(portName);
strFriendlyName = wstringToString(fname);
#else
strPortName = std::string(portName);
strFriendlyName = std::string(fname);
#endif
// remove (COMxx)
strFriendlyName = strFriendlyName.substr(0, strFriendlyName.find(("(COM")));
m_serialPortInfo.portName = strPortName;
m_serialPortInfo.description = strFriendlyName;
portInfoList.push_back(m_serialPortInfo);
}
if (ERROR_NO_MORE_ITEMS == GetLastError())
{
bRet = true; // no more item
}
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return bRet;
}
int main()
{
ULONGLONG startTick = 0;
startTick = GetTickCount64();
vector<SerialPortInfo> spInfo;
enumDetailsSerialPorts(spInfo);
for (int i = 0; i < spInfo.size(); i++)
{
std::cout << i << " - " << spInfo[i].portName << " " << spInfo[i].description << std::endl;
}
_tprintf(_T("time : %I64u ms"), GetTickCount64() - startTick);
return 0;
}
注意:
使用SetupDiGetDeviceInterfaceDetail替代SetupDiEnumDeviceInfo也能达到效果。参考:github.com/itas109/CSe…
License
License under CC BY-NC-ND 4.0: 署名-非商业使用-禁止演绎
如需转载请标明出处
QQ技术交流群:129518033
Refrence: