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

858 阅读4分钟

串口编程 - 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三种方式。

串口友好名称物理设备对象名称
COM1ELTIMA Virtual Serial Port\Device\VSerial7_0
COM2ELTIMA Virtual Serial Port\Device\VSerial7_1
COM4Prolific USB-to-Serial Comm Port\Device\ProlificSerial0
COM5Standard Serial over Bluetooth link\Device\BthModem0
COM6com0com - serial port emulator\Device\com0com11
COM7com0com - serial port emulator\Device\com0com21
CNCA0com0com - serial port emulator CNCA0\Device\com0com10
CNCB0com0com - 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:

  1. github.com/itas109/CSe…
  2. www.naughter.com/enumser.htm…