如何在C++中获取鼠标、键盘等HID设备的唯一标识符?
获取HID设备唯一标识符的C++实现方案
你提到的问题确实很常见——USB存储设备的实例ID(PnPID)通常基于硬件序列号保持唯一,但鼠标、键盘这类HID设备的实例ID会因为连接端口、系统枚举顺序等因素变化,没法直接用来做唯一标识。下面分享几种在Windows平台用C++获取HID设备真正唯一标识的方法:
核心思路:锁定硬件层面的唯一信息
我们需要获取设备本身的硬件属性,比如厂商ID(VID)+产品ID(PID)+硬件序列号的组合,这是设备生产时固化的信息,不会随系统环境变化。
方法1:结合SetupAPI与HID类函数直接读取序列号
这是最可靠的方式,通过Windows提供的HID专属函数直接从设备获取序列号,完美衔接你现有代码的枚举逻辑:
实现代码示例
#include <windows.h> #include <setupapi.h> #include <hidclass.h> #include <hidsdi.h> #include <wchar.h> #pragma comment(lib, "setupapi.lib") #pragma comment(lib, "hid.lib") void EnumerateHIDUniqueIDs() { GUID hidGuid; HidD_GetHidGuid(&hidGuid); // 获取HID设备类的GUID // 枚举所有已连接的HID设备 HDEVINFO hDevInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE) { MessageBox(NULL, L"无法获取设备信息列表", L"错误", MB_OK); return; } SP_DEVICE_INTERFACE_DATA deviceInterfaceData{}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); DWORD lCount = 0; // 延续你代码里的枚举逻辑 while (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &hidGuid, lCount++, &deviceInterfaceData)) { // 获取设备路径所需的缓冲区大小 DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize)); if (!pDetailData) continue; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); SP_DEVINFO_DATA devInfoData{}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { // 打开设备句柄 HANDLE hDevice = CreateFile( pDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if (hDevice != INVALID_HANDLE_VALUE) { // 1. 获取硬件唯一序列号 WCHAR serialNumber[256]{}; if (HidD_GetSerialNumberString(hDevice, serialNumber, sizeof(serialNumber))) { wprintf(L"设备唯一序列号: %s\n", serialNumber); } // 2. 获取厂商ID和产品ID(辅助验证唯一性) HIDD_ATTRIBUTES hidAttr{}; hidAttr.Size = sizeof(HIDD_ATTRIBUTES); if (HidD_GetAttributes(hDevice, &hidAttr)) { wprintf(L"厂商ID(VID): 0x%04X, 产品ID(PID): 0x%04X\n", hidAttr.VendorID, hidAttr.ProductID); } CloseHandle(hDevice); } // 可选:从注册表读取硬件ID用于对比 WCHAR hardwareID[256]{}; if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_HARDWAREID, NULL, reinterpret_cast<PBYTE>(hardwareID), sizeof(hardwareID), NULL)) { wprintf(L"硬件ID: %s\n", hardwareID); } } free(pDetailData); } SetupDiDestroyDeviceInfoList(hDevInfo); }
关键注意事项
- 权限要求:程序需要以管理员权限运行,否则可能无法打开HID设备或读取敏感注册表信息。
- 设备兼容性:部分廉价HID设备可能没有固化序列号,此时
HidD_GetSerialNumberString会返回失败,这种情况下可以退而求其次,用VID+PID+设备物理端口路径来区分,但这个组合在不同机器上可能不唯一,仅适合同一设备的本地识别。
方法2:解析HID报告描述符(进阶)
如果设备没有提供序列号,你可以尝试解析HID报告描述符,部分设备会在描述符中嵌入唯一标识信息。不过这种方式需要熟悉HID协议规范,实现成本较高,仅适合特殊场景。
总结
优先使用HidD_GetSerialNumberString获取硬件序列号,搭配VID和PID,就能得到跨系统的唯一标识;对于无序列号的设备,只能用本地唯一的组合方案。
内容的提问来源于stack exchange,提问作者MKallarakkal




