找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 3481|回复: 12

串口编程 - windows如何枚举串口|获取活跃串口号

 火.. [复制链接]
  • 打卡等级:即来则安
  • 打卡总天数:29
  • 打卡月天数:1
  • 打卡总奖励:7791
  • 最近打卡:2025-12-13 17:25:16

2540

主题

1353

回帖

2万

积分

管理员

积分
21301
发表于 2021-3-4 12:54:48 | 显示全部楼层 |阅读模式

串口编程 - windows如何枚举串口|获取活跃串口号
环境:
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三种方式。
串口友好名称物理设备对象名称COM1ELTIMA Virtual Serial Port\Device\VSerial7_0COM2ELTIMA Virtual Serial Port\Device\VSerial7_1COM4Prolific USB-to-Serial Comm Port\Device\ProlificSerial0COM5Standard Serial over Bluetooth link\Device\BthModem0COM6com0com - serial port emulator\Device\com0com11COM7com0com - serial port emulator\Device\com0com21CNCA0com0com - serial port emulator CNCA0\Device\com0com10CNCB0com0com - serial port emulator CNCB0\Device\com0com20

串口       友好名称COM1   ELTIMA Virtual Serial PortCOM2   ELTIMA Virtual Serial PortCOM4   Prolific USB-to-Serial Comm PortCOM5   Standard Serial over Bluetooth linkCOM6   com0com - serial port emulatoCOM7   com0com - serial port emulatorCNCA0  com0com - serial port emulator CNCB0  com0com - serial port emulator CNCB0
测试结果:
  • COMxxx遍历
0 - COM11 - COM22 - COM43 - COM54 - COM65 - COM7time : 375 ms
  • 注册表
0 - CNCA01 - CNCB02 - COM13 - COM24 - COM55 - COM66 - COM77 - COM4time : 0 m
  • 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 ms2.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/questi ... 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.portName << " " << spInfo.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/questi ... 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.portName << " " << spInfo.description << std::endl;        }        _tprintf(_T("time : %I64u ms"), GetTickCount64() - startTick);        return 0;}2.3 setupapi(GUID_DEVINTERFACE_COMPORT && SetupDiEnumDeviceInfo)
setupapi可以遍历设备管理器,因此可以用来枚举串口(包括友好名称、物理设备对象名称等),其本质上也是操作注册表。
代码:
// https://github.com/itas109/CSeri ... PortInfoWinBase.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){        // https://stackoverflow.com/questi ... 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 ... tupdienumdeviceinfo        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.portName << " " << spInfo.description << std::endl;        }        _tprintf(_T("time : %I64u ms"), GetTickCount64() - startTick);        return 0;}
注意:
使用SetupDiGetDeviceInterfaceDetail替代SetupDiEnumDeviceInfo也能达到效果。参考:https://github.com/itas109/CSerialPort/issues/41

Refrence:
  • https://github.com/itas109/CSerialPort
  • http://www.naughter.com/enumser.html

来源:IT小莫(头条)
原文:https://www.toutiao.com/a6923938837412446731/
工控课堂 www.gkket.com

0

主题

113

回帖

333

积分

注册会员

积分
333
发表于 2021-3-4 15:10:22 | 显示全部楼层
党的好公民,人民的好公仆。。。
工控课堂 www.gkket.com

0

主题

86

回帖

126

积分

新手上路

积分
126
发表于 2025-11-15 14:23:49 | 显示全部楼层
理性围观,感觉大家说得都有道理
工控课堂 www.gkket.com

0

主题

87

回帖

398

积分

注册会员

积分
398
发表于 2025-11-15 14:53:37 | 显示全部楼层
水个经验,支持楼主,加油呀
工控课堂 www.gkket.com

0

主题

89

回帖

149

积分

新手上路

积分
149
发表于 2025-11-15 15:08:06 | 显示全部楼层
救命!这波发言太秀了,直接原地封神~
疯狂认同!楼主说出了我不敢说的话
工控课堂 www.gkket.com

0

主题

92

回帖

276

积分

注册会员

积分
276
发表于 2025-11-15 15:44:32 | 显示全部楼层
评论区人才辈出,笑到停不下来😂
工控课堂 www.gkket.com

0

主题

169

回帖

503

积分

中级会员

积分
503
发表于 2025-11-15 17:18:08 | 显示全部楼层
蹲一波同款,有没有姐妹 / 兄弟推荐?
工控课堂 www.gkket.com

0

主题

84

回帖

124

积分

新手上路

积分
124
发表于 2025-11-15 17:28:13 | 显示全部楼层
谁懂啊!真的被戳中笑点 / 泪点了
工控课堂 www.gkket.com

0

主题

70

回帖

107

积分

新手上路

积分
107
发表于 2025-11-15 17:29:36 | 显示全部楼层
笑不活了,评论区比正文还精彩!
工控课堂 www.gkket.com

0

主题

125

回帖

190

积分

新手上路

积分
190
发表于 2025-11-15 17:31:19 | 显示全部楼层
已转发给朋友,一起感受这份快乐~
工控课堂 www.gkket.com
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|手机版|免责声明|本站介绍|工控课堂 ( 沪ICP备20008691号-1 )

GMT+8, 2025-12-22 17:29 , Processed in 0.095290 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表