串口编程 - windows如何枚举串口|获取活跃串口号
如需转载请标明出处:/itas109
QQ技术交流群:129518033
文章目录
串口编程 - windows如何枚举串口|获取活跃串口号前言1. windows枚举设备简介2. windows枚举串口代码实现2.1 CreateFile函数遍历2.2 注册表2.3 setupapi(GUID_DEVINTERFACE_COMPORT && SetupDiEnumDeviceInfo)环境:
OS: windows 10
compiler: VS
前言
进行串口通信,首先会面临串口枚举的问题。本文将介绍windows如何枚举串口。
1. windows枚举设备简介
一般而言,windows下枚举的串口可以分为如下几种类型:
物理串口:RS232虚拟串口:com0com 或 ELTIMA Virtual Serial Port 等蓝牙转串口USB转串口STM32虚拟COM端口 (设备管理器显示STMicroelectronics Virtual COM Port
)(未测试)
2. windows枚举串口代码实现
以下以三种主流方式实现枚举串口,包括CreateFile函数遍历、注册表和setupapi三种方式。
测试结果:
COMxxx遍历
0 - COM11 - COM22 - COM43 - COM54 - COM65 - COM7time : 375 ms
注册表
0 - CNCA01 - CNCB02 - COM13 - COM24 - COM55 - COM66 - COM77 - COM4time : 0 ms
setupapi
0 - COM6 com0com - serial port emulator1 - COM4 Prolific USB-to-Serial Comm Port2 - COM1 ELTIMA Virtual Serial Port3 - CNCA0 com0com - serial port emulator CNCA0 (CNCA0)4 - COM7 com0com - serial port emulator5 - COM5 Standard Serial over Bluetooth link6 - COM2 ELTIMA Virtual Serial Port7 - 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){// /questions/4804298/how-to-convert-wstring-into-stringif (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_UTF8return 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 UNICODEstrPortName = wstringToString(portName);#elsestrPortName = std::string(portName);#endifm_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){// /questions/4804298/how-to-convert-wstring-into-stringif (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_UTF8return ret;}bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList){///en-us/library/ms724256#define MAX_KEY_LENGTH 255#define MAX_VALUE_NAME 16383HKEY hKey;TCHARachValue[MAX_VALUE_NAME];// buffer for subkey nameDWORDcchValue = MAX_VALUE_NAME;// size of name stringTCHARachClass[MAX_PATH] = _T("");// buffer for class nameDWORDcchClassName = MAX_PATH;// size of class stringDWORDcSubKeys = 0;// number of subkeysDWORDcbMaxSubKey;// longest subkey sizeDWORDcchMaxClass;// longest class stringDWORDcKeyNum;// number of values for keyDWORDcchMaxValue;// longest value nameDWORDcbMaxValueData;// longest value dataDWORDcbSecurityDescriptor;// size of security descriptorFILETIMEftLastWriteTime;// last write timeint 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 handleachClass,// buffer for class name&cchClassName, // size of class stringNULL,// 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 timeif (!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 UNICODEstrPortName = wstringToString(strDSName);#elsestrPortName = std::string(strDSName);#endifm_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可以遍历设备管理器,因此可以用来枚举串口(包括友好名称、物理设备对象名称等),其本质上也是操作注册表。
代码:
// /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_COMPORTDEFINE_GUID(GUID_DEVINTERFACE_COMPORT, 0x86E0D1E0L, 0x8089, 0x11D0, 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73);#endifstruct SerialPortInfo{std::string portName;std::string description;};std::string wstringToString(const std::wstring& wstr){// /questions/4804298/how-to-convert-wstring-into-stringif (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_UTF8return ret;}bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList){// /en-us/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfobool 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 nameTCHAR 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_LENRegQueryValueEx(hDevKey, _T("PortName"), NULL, NULL, (BYTE*)portName, &dwCount);RegCloseKey(hDevKey);}// get friendly nameTCHAR fname[256];SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname,sizeof(fname), NULL);#ifdef UNICODEstrPortName = wstringToString(portName);strFriendlyName = wstringToString(fname);#elsestrPortName = 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
也能达到效果。参考:/itas109/CSerialPort/issues/41
License
License under CC BY-NC-ND 4.0: 署名-非商业使用-禁止演绎
如需转载请标明出处:/itas109
QQ技术交流群:129518033
Refrence:
/itas109/CSerialPort/enumser.html