能,但需绕开HID协议字面理解:通过SetupDiGetClassDevs枚举HID设备接口,用Device Interface Path调CreateFile(带\?前缀和共享标志),校验VID/PID,异步ReadFile+WriteFile或HidD_SetOutputReport操作报告数据。

能,但必须绕开“HID协议”字面理解——你实际操作的是 Windows 内置 HID 驱动暴露的用户态文件接口,不是直接跟 USB 端点打交道。
设备枚举必须用 SetupDiGetClassDevs + GUID_DEVINTERFACE_HID
不能靠设备管理器里看到的名字匹配,也不能硬拼 \.hid#vid_... 这种路径。系统更新后这类字符串会失效。正确流程是:
- 调
SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)获取设备信息集 - 循环调
SetupDiEnumDeviceInterfaces拿每个接口数据 - 对每个接口,调
SetupDiGetDeviceInterfaceDetail得到完整 Device Interface Path(含\?前缀) - 再用该路径调
SetupDiGetDeviceRegistryProperty读SPDRP_HARDWAREID,从中提取VID_XXXX&PID_YYYY做精确比对 - 漏掉
SetupDiDestroyDeviceInfoList会导致句柄泄漏,几分钟后ERROR_NO_MORE_ITEMS就开始报错
CreateFile 必须带 FILE_SHARE_READ | FILE_SHARE_WRITE 且路径带 \?
很多初学者卡在这一步:明明枚举到了设备,CreateFile 却返回 INVALID_HANDLE_VALUE,GetLastError() 是 ERROR_ACCESS_DENIED。这不是权限问题,是共享标志没开。尤其游戏手柄、带 LED 控制的键盘类设备,在独占模式下会拒绝后续预解析请求。
- 路径格式必须是
\?hid#vid_0483&pid_5740#...#{4d1e55b2-f16f-11cf-88cb-001111000030},缺\?前缀会失败 - 打开后立刻调
HidD_GetAttributes校验VendorID/ProductID,防止热插拔后句柄指向旧设备实例 - 别在 UI 线程里执行 —— 某些 USB 集线器上
CreateFile会卡 3–5 秒,界面直接假死
WriteFile 和 HidD_SetOutputReport 的缓冲区结构完全不同
这是最常翻车的地方:报告描述符里 bReportID 是 0 还是 1,直接决定你传进去的数据要不要带首字节。
立即学习“C++免费学习笔记(深入)”;
- 用
WriteFile:缓冲区首字节必须是Report ID(即使描述符里bReportID == 0,也要填 0),总长度包含它;Windows 会原样发到中断 OUT 端点 - 用
HidD_SetOutputReport:缓冲区首字节**不能**是Report ID,系统自动补;传入长度也不含它;某些指纹模块/定制固件只认这个 API,否则静默丢弃 -
HidD_SetOutputReport返回TRUE不代表设备收到了,得配合HidD_GetFeatureReport回读响应,或监听 IN 端点中断包确认
读取中断 IN 数据必须用 ReadFile + OVERLAPPED 异步模型
同步 ReadFile 容易阻塞,尤其设备没发数据时会一直等下去。真实项目必须用异步 I/O 配合事件或完成端口。
- 创建句柄时加
FILE_FLAG_OVERLAPPED - 每次
ReadFile前重置OVERLAPPED结构体的hEvent或Internal字段 - 收到数据后,
ReadFile返回FALSE且GetLastError() == ERROR_IO_PENDING是正常现象;等事件触发或完成端口通知后再调GetOverlappedResult - 缓冲区大小要严格按报告描述符定义的
wMaxPacketSize对齐(通常是 64 字节),少一字节都可能收不到整包
真正难的从来不是写几行 WriteFile,而是设备端报告描述符怎么写、中断端点怎么配、帧协议怎么设计校验和与超时重传——这些细节一旦错位,上位机代码再严谨也收不到有效数据。


















