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。
为了能重复利用这俩表,可以设置两个指针分别指向这两个数组结构,对数组的操作都由指针实现。每次对比完都交换一下这俩指针,这样本次的“新”状态表到下次时间到的时候就成了“旧”表,类似于图形领域的双缓冲。

浙公网安备 33010602011771号