Win32 API 操作串口

获取串口名字

std::vector<std::wstring> GetPnpSerialPortNames(void) {
	std::vector<std::wstring> ret;
	wchar_t desc[100], port[100];

	HDEVINFO devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_PORTS, NULL, NULL, DIGCF_PRESENT);
	if (devinfo == INVALID_HANDLE_VALUE) {
		return ret;
	}
	SP_DEVINFO_DATA infodata{};
	infodata.cbSize = sizeof(SP_DEVINFO_DATA);

	BOOL bOK = TRUE;
	for (DWORD index = 0; ; index++) {
		bOK = SetupDiEnumDeviceInfo(devinfo, index, &infodata);
		if (!bOK) {
			if (GetLastError() == ERROR_NO_MORE_ITEMS)
				bOK = TRUE;
			break;
		}

		DWORD dwsize1 = sizeof desc;
		ZeroMemory(desc, sizeof desc);
		bOK = SetupDiGetDeviceRegistryPropertyW(devinfo, &infodata, SPDRP_DEVICEDESC, NULL, (PBYTE)desc, sizeof desc, &dwsize1);
		if (!bOK)
			continue;

		HKEY k = SetupDiOpenDevRegKey(devinfo, &infodata, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
		if (k == INVALID_HANDLE_VALUE)
			continue;

		DWORD dwsize2 = sizeof port;
		ZeroMemory(port, sizeof port);
		if (RegGetValueW(k, NULL, L"PortName", RRF_RT_REG_SZ, NULL, (PVOID)port, &dwsize2) != ERROR_SUCCESS)
			continue;

		wsprintfW(port, L"%s %s", port, desc); // 类似“COM3 (USB SERIAL CH340)”这样的。
		ret.push_back(port);
	}
	SetupDiDestroyDeviceInfoList(devinfo);

	if (!bOK) ret.clear();
	return ret;
}
  • 这种方法不一定获取到全部端口,因为一些虚拟串口会创建自己的设备类、枚举、guid之类的配置。
  • 这种方法可以辨别一个端口是USB串口、蓝牙,或者别的。把SPDRP_DEVICEDESC替换成别的参数就可以了。

用注册表查串口名

std::vector<std::wstring> GetRegSerialPortNames(void)
{
	std::vector<std::wstring> ret;

	HKEY k = NULL;
	LSTATUS st = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &k);
	if (st != ERROR_SUCCESS)
		return ret;

	wchar_t name[100], value[100];
	for (DWORD index = 0; ; index++)
	{
		ZeroMemory(name, sizeof name);
		ZeroMemory(value, sizeof value);
		DWORD n_name = 100, n_value = sizeof value;
		st = RegEnumValueW(k, index, name, &n_name, NULL, NULL, (LPBYTE)&value[0], &n_value);
		if (st != ERROR_SUCCESS){
			if (st == ERROR_NO_MORE_ITEMS)
				st = ERROR_SUCCESS;
			break;
		}
		ret.push_back(value); //类似“COM2”这样的。
	}

	if (st != ERROR_SUCCESS) ret.clear();
	return ret;
}
  • 这种方法得到的端口名可以直接送给CreateFile使用。
  • 没办法辨别设备类型。

合并二者

	auto pnp = GetPnpSerialPortNames(); // 第一种方法的结果
	auto ker = GetRegSerialPortNames(); // 第二种
	const int ny = (const int) ker.size();
	for (int i = 0; i < ny; i++) {
		std::wstring const& s = ker.at(i);
		auto cmp = [s](std::wstring const& t)->bool{ return t.substr(0, s.length()).compare(s) == 0;};
		if (std::find_if(pnp.begin(), pnp.end(), cmp) == pnp.end()){ pnp.push_back(s);} // 没有就插入
	}

效果

对串口插拔做出反应

使用“SetTimer”设置200毫秒定时器查询串口,得到"COM 2,5,...N",和旧表对比一下,就知道哪个被插入哪个被拔出了。
这种方法需要两个表,例如"BYTE Map1[100+1], Map2[100+1]"。
若com1存在,就把map[1]设置为非零,原理很简单。设置101是因为windows里面的N最大是100。
为了能重复利用这俩表,可以设置两个指针分别指向这两个数组结构,对数组的操作都由指针实现。每次对比完都交换一下这俩指针,这样本次的“新”状态表到下次时间到的时候就成了“旧”表,类似于图形领域的双缓冲。

posted @ 2025-11-06 19:35  fish21  阅读(4)  评论(0)    收藏  举报