You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

火山引擎 最新活动