C++ USB转串口编程 - 查找COM口

环境:Visual Studio 2017 + Windows 10

参考文档:

https://docs.microsoft.com/zh-cn/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=msvc-160

https://blog.csdn.net/shufac/article/details/82892371?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

1. 新建项目,选择C++和DLL生成空项目SerialPort

2. 新建头文件“SerialPort.h”

  • 添加宏定义,
#ifdef SERIALPORT_EXPORTS
#define SERIALPORT_API __declspec(dllexport)
#else
#define SERIALPORT_API __declspec(dllimport)
#endif
  • 在生成 DLL 项目里面的预处理器定义有定义SERIALPORT_EXPORTS ,此修饰符指示编译器和链接器从 DLL 导出函数或变量,以便其他应用程序可以使用它。

  • 定义API函数返回状态值,后面再添加更多的错误值。
enum eSerialPortStatus
{
	SERIALPORT_OK = 0,
	SERIALPORT_PARAMETER_ERROR,
	SERIALPORT_NO_DEVICE,
};
  • 添加第一个API函数USB2SerialSearchPort,通过USB的PID和VID获取对应的COM口编号
extern "C" SERIALPORT_API eSerialPortStatus USB2SerialSearchPort(
	unsigned short vid,
	unsigned short pid,
	unsigned char *port
);

3. 在SerialPort.cpp添加

  • include需要用的头文件
#include "stdafx.h"
#include <atlstr.h>

#include <SetupAPI.h>
#pragma comment(lib, "setupapi.lib")

#include "SerialPort.h"
  • 修改工程属性字符集属性为“使用多字节字符集”

  • 实现API函数USB2SerialSearchPort

a. 调用API函数SetupDiGetClassDevs(用来查询与指定参数匹配的所有已安装设备)。

DWORD dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;

hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, dwFlag);
if (INVALID_HANDLE_VALUE == hDevInfo)
    return SERIALPORT_NO_DEVICE;

DIGCF_ALLCLASSES 
  返回所有已安装设备的列表或所有设备接口类。

DIGCF_PRESENT 
  只返回当前系统中存在的(已连接)设备。

b. 将VID和PID转换为搜索关键字字符串,并大写化。

CString strVidPid;
strVidPid.Format(_T("VID_%04x&PID_%04x"), vid, pid);
strVidPid.MakeUpper();

c. 遍历所有设备,找到对应VID和PID设备

for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
{

}

API函数SetupDiEnumDeviceInfo  枚举指定设备信息集合的成员,并将数据放在PSP_DEVINFO_DATA中

for循环内先通过SetupDiGetDeviceInstanceId获取设备的实例ID,ID的字符串放在数组szDis中,然后szDis是否包含VID和PID字符串

DWORD nSize = 0;
TCHAR szDis[MAX_PATH] = { 0x00 };// 存储设备实例ID
TCHAR szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
// 无效设备
if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
{
    continue;
}

CString strTemp, strName;
// 根据设备信息寻找VID PID一致的设备
strTemp.Format(_T("%s"), szDis);
strTemp.MakeUpper();
if (strTemp.Find(strVidPid, 0) == -1)
{
    continue;
}

找到这个设备后,调用API函数SetupDiGetDeviceRegistryProperty获取指定的即插即用设备属性。

nSize = 0;
SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
    SPDRP_FRIENDLYNAME,
    0, (PBYTE)szFN,
    sizeof(szFN),
    &nSize);

// "XXX Virtual Com Port (COM?)"
strName.Format(_T("%s"), szFN);
if (strName.IsEmpty())
{
    continue;
}

SPDRP_CLASS:The function retrieves a REG_SZ string that contains the device setup class of a device. 返回类型,FT4232H返回“USB”

SPDRP_CLASSGUID:The function retrieves a REG_SZ string that contains the GUID that represents the device setup class of a device. 返回GUID字符串,FT4232H返回 "{36fc9e60-c465-11cf-8056-444553540000}"

SPDRP_DEVICEDESC:The function retrieves a REG_SZ string that contains the description of a device. FT4232H返回"USB Serial Converter C"

SPDRP_ENUMERATOR_NAME:The function retrieves a REG_SZ string that contains the name of the device's enumerator.  FT4232H返回“USB”

SPDRP_FRIENDLYNAME:The function retrieves a REG_SZ string that contains the friendly name of a device. 用这个参数对于FTDI的芯片来说,读不到相应信息。这个参数应该是对应设备管理器中的这个属性:

SPDRP_LOCATION_INFORMATION:The function retrieves a REG_SZ string that contains the hardware location of a device.  FT4232H返回"0000.0014.0000.004.004.004.000.000.000"

SPDRP_MFG:The function retrieves a REG_SZ string that contains the name of the device manufacturer. 得到制造商字符串。FT4232H返回“FTDI”

SPDRP_PHYSICAL_DEVICE_OBJECT_NAME: The function retrieves a REG_SZ string that contains the name that is associated with the device's PDO.FT4232H返回"\\Device\\0000025a"

SPDRP_SECURITY_SDS: The function retrieves a REG_SZ string that contains the device's security descriptor.

SPDRP_SERVICE:The function retrieves a REG_SZ string that contains the service name for a device. FT4232H返回"FTDIBUS"

最后再szFN字符串中找到字符"COM",从而找到对应串口的COM号,将结果保存在port数组内。

int nStart = -1;
int nEnd = -1;
// 寻找串口信息
nStart = strName.Find(_T("(COM"), 0);
nEnd = strName.Find(_T(")"), 0);
if (nStart == -1 || nEnd == -1)
{
    continue;
}

strTemp = strName.Mid(nStart + 4, nEnd - nStart - 2);
port[n] = atoi(strTemp);
n++;

遍历完所有设备后返回,如果n等于0,返回No Device,否则返回OK。

SetupDiDestroyDeviceInfoList(hDevInfo);
hDevInfo = INVALID_HANDLE_VALUE;
if (n > 0)
	return SERIALPORT_OK;
else
	return SERIALPORT_NO_DEVICE;

4. 新建一个C#项目SerialPortClient来验证DLL,新建一个类SerialPort.cs,引入API函数

using System.Runtime.InteropServices;
public enum eSerialPortStatus
{
    SERIALPORT_OK = 0,
    SERIALPORT_PARAMETER_ERROR,
    SERIALPORT_NO_DEVICE,
};

[DllImport("SerialPort.dll")]
public static extern eSerialPortStatus USB2SerialSearchPort(
    UInt16 vid,
    UInt16 pid,
    byte[] port
);

调用API函数

UInt16 vid = (UInt16)Convert.ToInt32(tbVID.Text, 16);
UInt16 pid = (UInt16)Convert.ToInt32(tbPID.Text, 16);
byte[] port = new byte[128];
SerialPort.USB2SerialSearchPort(vid, pid, port);
for (byte i = 0; i < 128; i++)
{
    if (port[i] == 0)
        break;
    Console.WriteLine("COM" + port[i]);
}

以上的方法并不能识别FT4232H,也有说FTDI的芯片都认不到。试另外一种方式,从注册表中找到对应的COM口(只在Windows 10 64位上验证)。

方法:

插入USB转串口设备,FT4232H会生成4路串口,例如COM12-COM15,在注册表中查找COM12,可以发现2个地方的COM12有特殊意义。

“\HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM”:这里面的COM口是表示当前存在设备的COM口

"\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\COM Name Arbiter\Devices":这里面是COM口对应的VID和PID

这样就把VID/PID和COM口对应起来了。

a. 找到VID/PID一致的COM口,保存在一个数组里

CString strVID, strPID;
strVID.Format(_T("%04X"), vid);
strPID.Format(_T("%04X"), pid);

strTemp.Format(_T("SYSTEM\\CurrentControlSet\\Control\\COM Name Arbiter\\Devices"));
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, strTemp, NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
{
	byte index = 0;
	for (byte i = 1; i < 128; i++)
	{
		strTemp.Format(_T("COM%d"), i);
		DWORD	lValue = MAX_PATH;
		TCHAR	szValue[MAX_PATH];
		lRtn = RegQueryValueEx(hKey, strTemp, NULL, &lValue, (LPBYTE)szValue, &lValue);
		if (lRtn == ERROR_SUCCESS)
		{
			strTemp.Format(_T("%s"), szValue);
			strTemp.MakeUpper();
			if ((strTemp.Find(strVID, 0) != -1) && (strTemp.Find(strPID, 0) != -1))
			{
				allPort[index++] = i;
			}
		}
	}
	allPort[index] = 0;
	RegCloseKey(hKey);
}

b. 在上面数组里面找到当前的设备

strTemp.Format(_T("HARDWARE\\DEVICEMAP\\SERIALCOMM"));
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, strTemp, NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
{
	TCHAR	szName[MAX_PATH];
	int i = 0;
	int index = 0;
	for (;;)
	{
		DWORD nLen = 256;
		DWORD vLen = 256;
		DWORD kType;
		TCHAR szValue[MAX_PATH];
		if (RegEnumValue(hKey, i, szName, &nLen, 0, &kType, (PBYTE)szValue, &vLen) == ERROR_SUCCESS)
		{
			if (kType == REG_SZ)
			{
				for (int j = 0; ; j++)
				{
					if (allPort[j] == 0)
						break;
					strTemp.Format(_T("%s"), szValue);
					CString strCom;
					strCom.Format(_T("COM%d"), allPort[j]);
					if (strTemp == strCom)
						port[index++] = allPort[j];
				}
			}
		}
		else
			break;
		++i;
	}
	port[index] = 0;
	RegCloseKey(hKey);
}

 

 

 

 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值