SDL/HIDAPI 的 Windows Preparsed Data 导出工具 pp_data_dump:原理、文件格式与离线报告描述符重构测试
SDL/HIDAPI 的 Windows Preparsed Data 导出工具 pp_data_dump原理、文件格式与离线报告描述符重构测试【免费下载链接】SDLSimple DirectMedia Layer项目地址: https://gitcode.com/GitHub_Trending/sd/SDL导读本文围绕 SDL 仓库内 HIDAPI 子系统src/hidapi自带的 Windows 命令行工具pp_data_dump.exe展开讲解它如何把 Windows HID 子系统内部不透明的_HIDP_PREPARSED_DATA结构以人类可读的文本形式导出为.pp_data文件以及这份文件如何被hid_report_reconstructor_test.exe单元测试离线消费用于在没有真实硬件的情况下验证 HID 报告描述符重构逻辑。读完本文你将掌握该工具的完整使用方式、.pp_data文本格式的每一个字段含义、其背后对应的源码实现以及如何用仓库自带的测试用例和示例数据复现整套导出—重构—比对工作流。一、工具定位把 Windows 不透明的 Preparsed Data 变成可读文本Windows HID 子系统会把设备上报的 HID Report Descriptor 解析成一个被称为Preparsed Data预解析数据的结构体。应用程序通过HidD_GetPreparsedData()获取它的句柄再配合HidP_GetCaps()、HidP_GetValueCaps()等 API 查询其中的能力信息。但这个结构体对开发者来说是不透明的_HIDP_PREPARSED_DATA的内部布局被微软保留为仅供系统内部使用官方文档不公开其内存布局。HIDAPI 在 src/hidapi/windows/hidapi_descriptor_reconstruct.h 中自行定义了一套与_HIDP_PREPARSED_DATA二进制布局兼容的结构体hidp_preparsed_data、hid_pp_cap、hid_pp_link_collection_node等以便从 Preparsed Data 反推出原始 HID Report Descriptor即描述符重构功能实现在 src/hidapi/windows/hidapi_descriptor_reconstruct.c 中。pp_data_dump.exe 正是这座桥梁它直接读取真实设备的 Preparsed Data把 HIDAPI 内部结构体的全部字段逐一格式化输出到文本文件。这份文本既方便人阅读又可以被重构逻辑的单元测试程序重新解析回内存结构从而在没有硬件设备连接的情况下完成离线测试。二、使用方法零参数即插即用pp_data_dump.exe是一个没有命令行参数的 Windows 命令行小工具。使用流程非常简单把目标 HID 设备连接到 Windows 主机在命令行中直接执行pp_data_dump.exe工具会枚举系统中所有已连接的 HID 设备并针对每个设备的每一个 Top-Level Collection生成一个文件。启动时它会打印编译期与运行期 HIDAPI 版本信息并逐一输出枚举到的设备概要厂商 ID、产品 ID、路径、序列号、厂商字符串、产品字符串、Release 号、接口号、Usage/Usage Page例如源码 src/hidapi/windows/pp_data_dump/pp_data_dump.c 中所示pp_data_dump tool. Compiled with hidapi version X, runtime version X. Device Found type: 046d b010 path: \\?\hid#... serial_number: ... Manufacturer: Logitech Product: Logitech Bluetooth Wireless Mouse Release: 0 Interface: -1 Usage (page): 01 (0C)输出文件命名规则每个文件按以下格式命名源码 pp_data_dump.c 中以%04X_%04X_%04X_%04X.pp_data生成即全部使用大写的 4 位十六进制vendor_id_product_id_usage_usage_table.pp_data占位符含义示例vendor_id厂商 IDVID046Dproduct_id产品 IDPIDB010usage顶层集合的 Usage0001usage_table顶层集合的 Usage PageUsage 表000C例如046D_B010_0001_000C.pp_data表示罗技LogitechVID0x046D产品0xB010上 Usage Page0x000CConsumer消费类页面、Usage0x0001Consumer Control的顶层集合。仓库测试数据目录 src/hidapi/windows/test/data 中存放的正是历史上用该工具采集的真实设备数据。三、文件内容详解一份 .pp_data 长什么样.pp_data文件是纯文本格式逐字段复刻了 HIDAPI 内部用来表示 Preparsed Data 的结构体。以下完整示例取自仓库测试数据 src/hidapi/windows/test/data/046D_B010_0001_000C.pp_data与 README 中给出的样例一致对应一台罗技蓝牙无线鼠标的 Consumer Control 集合# HIDAPI device info struct: dev-vendor_id 0x046D dev-product_id 0xB010 dev-manufacturer_string Logitech dev-product_string Logitech Bluetooth Wireless Mouse dev-release_number 0x0000 dev-interface_number -1 dev-usage 0x0001 dev-usage_page 0x000C dev-path \\?\hid#{00001124-0000-1000-8000-00805f9b34fb}_vid0002046d_pidb010col02#81cf1c1b930001#{4d1e55b2-f16f-11cf-88cb-001111000030} # Preparsed Data struct: pp_data-MagicKey 0x48696450204B4452 pp_data-Usage 0x0001 pp_data-UsagePage 0x000C pp_data-Reserved 0x00000000 # Input caps_info struct: pp_data-caps_info[0]-FirstCap 0 pp_data-caps_info[0]-LastCap 1 pp_data-caps_info[0]-NumberOfCaps 1 pp_data-caps_info[0]-ReportByteLength 2 # Output caps_info struct: pp_data-caps_info[1]-FirstCap 1 pp_data-caps_info[1]-LastCap 1 pp_data-caps_info[1]-NumberOfCaps 0 pp_data-caps_info[1]-ReportByteLength 0 # Feature caps_info struct: pp_data-caps_info[2]-FirstCap 1 pp_data-caps_info[2]-LastCap 1 pp_data-caps_info[2]-NumberOfCaps 0 pp_data-caps_info[2]-ReportByteLength 0 # LinkCollectionArray Offset Size: pp_data-FirstByteOfLinkCollectionArray 0x0068 pp_data-NumberLinkCollectionNodes 1 # Input hid_pp_cap struct: pp_data-cap[0]-UsagePage 0x0006 pp_data-cap[0]-ReportID 0x03 pp_data-cap[0]-BitPosition 0 pp_data-cap[0]-BitSize 8 pp_data-cap[0]-ReportCount 1 pp_data-cap[0]-BytePosition 0x0001 pp_data-cap[0]-BitCount 8 pp_data-cap[0]-BitField 0x02 pp_data-cap[0]-NextBytePosition 0x0002 pp_data-cap[0]-LinkCollection 0x0000 pp_data-cap[0]-LinkUsagePage 0x000C pp_data-cap[0]-LinkUsage 0x0001 pp_data-cap[0]-IsMultipleItemsForArray 0 pp_data-cap[0]-IsButtonCap 0 pp_data-cap[0]-IsPadding 0 pp_data-cap[0]-IsAbsolute 1 pp_data-cap[0]-IsRange 0 pp_data-cap[0]-IsAlias 0 pp_data-cap[0]-IsStringRange 0 pp_data-cap[0]-IsDesignatorRange 0 pp_data-cap[0]-Reserved1 0x000000 pp_data-cap[0]-pp_cap-UnknownTokens[0].Token 0x00 pp_data-cap[0]-pp_cap-UnknownTokens[0].Reserved 0x000000 pp_data-cap[0]-pp_cap-UnknownTokens[0].BitField 0x00000000 pp_data-cap[0]-pp_cap-UnknownTokens[1].Token 0x00 pp_data-cap[0]-pp_cap-UnknownTokens[1].Reserved 0x000000 pp_data-cap[0]-pp_cap-UnknownTokens[1].BitField 0x00000000 pp_data-cap[0]-pp_cap-UnknownTokens[2].Token 0x00 pp_data-cap[0]-pp_cap-UnknownTokens[2].Reserved 0x000000 pp_data-cap[0]-pp_cap-UnknownTokens[2].BitField 0x00000000 pp_data-cap[0]-pp_cap-UnknownTokens[3].Token 0x00 pp_data-cap[0]-pp_cap-UnknownTokens[3].Reserved 0x000000 pp_data-cap[0]-pp_cap-UnknownTokens[3].BitField 0x00000000 pp_data-cap[0]-NotRange.Usage 0x0020 pp_data-cap[0]-NotRange.Reserved1 0x0020 pp_data-cap[0]-NotRange.StringIndex 0 pp_data-cap[0]-NotRange.Reserved2 0 pp_data-cap[0]-NotRange.DesignatorIndex 0 pp_data-cap[0]-NotRange.Reserved3 0 pp_data-cap[0]-NotRange.DataIndex 0 pp_data-cap[0]-NotRange.Reserved4 0 pp_data-cap[0]-NotButton.HasNull 0 pp_data-cap[0]-NotButton.Reserved4 0x000000 pp_data-cap[0]-NotButton.LogicalMin 0 pp_data-cap[0]-NotButton.LogicalMax 100 pp_data-cap[0]-NotButton.PhysicalMin 0 pp_data-cap[0]-NotButton.PhysicalMax 0 pp_data-cap[0]-Units 0 pp_data-cap[0]-UnitsExp 0 # Output hid_pp_cap struct: # Feature hid_pp_cap struct: # Link Collections: pp_data-LinkCollectionArray[0]-LinkUsage 0x0001 pp_data-LinkCollectionArray[0]-LinkUsagePage 0x000C pp_data-LinkCollectionArray[0]-Parent 0 pp_data-LinkCollectionArray[0]-NumberOfChildren 0 pp_data-LinkCollectionArray[0]-NextSibling 0 pp_data-LinkCollectionArray[0]-FirstChild 0 pp_data-LinkCollectionArray[0]-CollectionType 1 pp_data-LinkCollectionArray[0]-IsAlias 0 pp_data-LinkCollectionArray[0]-Reserved 0x000000003.1 文件的三段式结构从格式上可以清晰地拆成三个段落设备信息头HIDAPI device info struct以dev-前缀开头来源于hid_enumerate()返回的hid_device_info字段记录了 VID/PID、厂商/产品字符串、Release、接口号、Usage/Usage Page 以及完整的设备路径。测试程序用空行标记该段的结束。Preparsed Data 总览pp_data-顶层字段MagicKey、Usage、UsagePage、Reserved、三个caps_info分别对应 Input、Output、Feature 三种报告类型以及 Link Collection 数组的偏移与数量。能力数组与 Link Collection 明细pp_data-cap[n]每个能力的完整描述与pp_data-LinkCollectionArray[n]集合树的节点信息。3.2 关键字段语义pp_data-MagicKeyPreparsed Data 的魔数标识0x48696450204B4452是 ASCII 的 HidP KDR用于校验结构合法性caps_info[0..2]分别描述 Input / Output / Feature 报告的区间与布局其中FirstCap/LastCap构成半开区间[FirstCap, LastCap)指示能力数组的有效遍历范围NumberOfCaps包括LastCap之后的空槽位ReportByteLength是该类报告的字节长度。注意本示例中 Output/Feature 的FirstCap LastCap 1表示区间为空、没有任何能力——所以文件后面出现# Output hid_pp_cap struct: 后没有任何内容cap[n]的能力字段BitPosition、BitSize源码内部名称为ReportSize、ReportCount、BytePosition、BitCount、BitField、NextBytePosition描述该能力在报告位流中的位置与大小LinkCollection、LinkUsagePage、LinkUsage把它挂到集合树上8 个布尔标志位IsMultipleItemsForArray、IsButtonCap、IsPadding、IsAbsolute、IsRange、IsAlias、IsStringRange、IsDesignatorRange压缩在同一个字节中对应 hidapi_descriptor_reconstruct.h 的位域声明Range/NotRange联合体当IsRange为真时输出Range.UsageMin/UsageMax、StringMin/Max、DesignatorMin/Max、DataIndexMin/Max否则输出NotRange单值形式本示例即单值形式Usage 为0x0020即 Consumer Control 页面下的 AC PanButton/NotButton联合体IsButtonCap为真时只有 LogicalMin/Max 有意义否则本示例输出HasNull、Logical/Physical Min/Max 等值字段信息pp_cap-UnknownTokens[4]HIDAPI 为尚未被完全解析的未知全局项预留的槽位每个 Token 记录该项的单字节前缀Token、保留字节和BitField数据部分用于重构时无损保留无法识别的描述符项LinkCollectionArray[n]集合树节点Parent/NumberOfChildren/NextSibling/FirstChild构成兄弟/孩子链表结构对应 hidapi_descriptor_reconstruct.hCollectionType用 8 位位域表示集合类型值为 1 对应 Application Collection。四、源码实现从枚举设备到逐字段落盘pp_data_dump的完整实现只有一份 C 源文件 src/hidapi/windows/pp_data_dump/pp_data_dump.c其工作流程可以拆成四步初始化与枚举调用hid_init()初始化 HIDAPI随后hid_enumerate(0x0, 0x0)枚举全部 HID 设备VID/PID 均传 0 表示不筛选打印每个设备的信息打开设备并创建输出文件对每个枚举结果调用hid_open_path(cur_dev-path)打开设备按第二节的规则拼接出文件名并用fopen_s创建文本文件导出 Preparsed Data调用 Windows APIHidD_GetPreparsedData()拿到PHIDP_PREPARSED_DATAcast 为 HIDAPI 自定义的hidp_preparsed_data*后逐字段格式化输出dump_pp_data()清理HidD_FreePreparsedData()释放结构hid_close()关闭设备遍历链表直至hid_free_enumeration()释放枚举结果最后hid_exit()。其中第 3 步是核心值得展开能力数组通过offsetof(hidp_preparsed_data, caps)得到首地址再按caps_info[i].FirstCap到caps_info[i].LastCap的区间分别遍历 Input、Output、Feature 三类能力并调用dump_hid_pp_cap()pp_data_dump.cLink Collection 数组则通过caps首地址加上pp_data-FirstByteOfLinkCollectionArray偏移量定位pp_data_dump.c印证了 hidapi_descriptor_reconstruct.h 中caps与LinkCollectionArray共享同一段柔性数组成员union的内存布局设计对布尔标志位、位域字段的打印做了编译器兼容处理例如 Link Collection 的位域统一强转为unsigned int再输出注释说明这是因为不同编译器对ULONG位域的符号性处理不一致pp_data_dump.c。五、下游消费方hid_report_reconstructor_test 离线单元测试.pp_data文件最重要的用途是作为 HIDAPI 的 Windows 报告描述符重构器report descriptor reconstructor的离线测试输入。对应测试程序是 src/hidapi/windows/test/hid_report_reconstructor_test.c它实现了 README 中所说的hid_report_reconstructor_test.exe。测试的工作方式是以两个参数运行hid_report_reconstructor_test xxx.pp_data xxx_expected.rpt_desc测试程序用sscanf逐行解析.pp_data文本alloc_preparsed_data_from_file()见 hid_report_reconstructor_test.c依据FirstByteOfLinkCollectionArray和NumberLinkCollectionNodes动态分配内存重建出与真实设备内存布局一致的hidp_preparsed_data调用hid_winapi_descriptor_reconstruct_pp_data()重构出 HID Report Descriptor 字节流把_expected.rpt_desc以0x.., 0x..十六进制文本形式存储的期望描述符见 046D_C52F_0001_000C_expected.rpt_desc解析为字节数组与重构结果逐字节比对完全一致则测试通过。例如上节示例中罗技鼠标那一个 Input 能力重构出的期望描述符片段为0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01, 0x85, 0x03, 0x19, 0x01, 0x2A, 0x8C, 0x02, 0x15, 0x01, 0x26, 0x8C, 0x02, 0x75, 0x10, 0x95, 0x02, 0x81, 0x00, 0xC0,对照标准 HID 项编码可以逐项验证0x05 0x0CUsage Page Consumer、0x09 0x01Usage Consumer Control、0xA1 0x01Application Collection 开始、0x85 0x03Report ID 3、0x19/0x2AUsage 范围 1..396、0x15/0x26Logical 范围 1..396、0x75/0x95Report Size 16、Report Count 2、0x81 0x00Input Data 项、0xC0集合结束——与.pp_data中记录的ReportID0x03、Usage0x0020不是范围起点、LogicalMax100等字段之间存在明确的对应关系这正是Preparsed Data → 描述符重构要还原的信息。5.1 测试用例的注册机制在 src/hidapi/windows/test/CMakeLists.txt 中通过HID_DESCRIPTOR_RECONSTRUCT_TEST_CASES列表注册了 23 组测试用例046D_C52F_0001_000C、17CC_1130_0000_FF01、046A_0011_0006_0001等每组用例要求存在两个文件name.pp_data—— Preparsed Data 的文本表示必需name_expected.rpt_desc—— 重构出的期望 HID Report Descriptor必需name_real.rpt_desc—— 可选的真实原始描述符用于人工对照非测试必需。每个用例通过add_test()注册为WinHidReportReconstructTest_TEST_CASE形式的 CTest 用例WORKING_DIRECTORY指向 hidapi 动态库输出目录若开启了 ASanHIDAPI_ENABLE_ASAN还会为 MSVC 追加工具链目录到 PATH 并设置ASAN_SAVE_DUMPS环境变量以便收集崩溃转储。测试目录下的.pp_data数据覆盖了多种真实设备Logitech 鼠标/键盘、Plantronics 耳机、Dell 键盘、Steam 控制器等涉及000CConsumer、FF00/FF01厂商自定义、0001Generic Desktop、0006Generic Device、000BTelephony等多个 Usage Page为重构器提供了多样的输入覆盖。六、构建与运行pp_data_dump与测试程序都通过 CMake 构建二者均要求 C11 标准src/hidapi/windows/pp_data_dump/CMakeLists.txtadd_executable(pp_data_dump pp_data_dump.c)链接hidapi_winapi库并可通过install()安装到CMAKE_INSTALL_BINDIRsrc/hidapi/windows/test/CMakeLists.txt构建hid_report_reconstructor_test链接hidapi_include与hidapi_winapi。典型流程在 Windows 上使用 CMake Visual Studio 或 MinGWcmake -B build -S . -DHIDAPI_BUILD_TESTSON cmake --build build --config Release ctest --test-dir build -C Release -R WinHidReportReconstructTestpp_data_dump.c顶部还针对 MinGW 做了预处理定义__USE_MINGW_ANSI_STDIO以正确支持%hh等 ANSI 长度修饰符pp_data_dump.c测试程序同样对%zu做了兼容处理说明这两个工具都兼顾了 MSVC 与 MinGW 工具链。七、局限性与注意事项_HIDP_PREPARSED_DATA是未公开结构pp_data_dump与重构器所依赖的内存布局是 HIDAPI 以 Chromium 项目中hid_preparsed_data.cc的解析实现为参考推导出来的。微软不保证该结构在未来的 Windows 版本中保持稳定因此该工具链的二进制兼容性存在天然风险结构与版本绑定源码中对hid_pp_link_collection_node有sizeof 16的编译期断言见 hidapi_descriptor_reconstruct.h一旦编译器产生的内存布局与预期不符会直接编译失败这正体现了对该未公开结构二进制布局的高度敏感输出文件数量工具会为每个已连接设备的每个 Top-Level Collection生成一个文件设备较多或集合较多时会产生大量文件文件生成在程序当前工作目录下运行环境该工具仅面向 Windows依赖hid.c与 Windows HID API且需要真实硬件连接才能采集数据而hid_report_reconstructor_test则完全离线运行仅依赖.pp_data文本。八、总结pp_data_dump.exe是 HIDAPI Windows 后端一套完整的采集—重构—验证方法论中的采集环节它将微软未公开的 Preparsed Data 结构固化为 HIDAPI 自定义结构的可读文本使开发者得以理解 Windows 如何解释 HID Report Descriptor而配套的hid_report_reconstructor_test.exe与 src/hidapi/windows/test/data 中 23 组真实设备样本则让报告描述符重构逻辑可以在无硬件环境下反复回归验证。这套工具链既是排查 Windows HID 设备枚举问题的实用抓手也是理解 HID Report Descriptor 与 Preparsed Data 之间双向映射关系的最佳学习材料。【免费下载链接】SDLSimple DirectMedia Layer项目地址: https://gitcode.com/GitHub_Trending/sd/SDL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →