首页 游戏 软件 资讯 排行榜 专题
首页
编程语言
Linux下C++读取HID设备报告描述符完整指南

Linux下C++读取HID设备报告描述符完整指南

热心网友
96
转载
2026-05-06

C++如何读取Linux下的HID设备原始报告描述符【深度】

c++如何读取Linux下的HID设备原始报告描述符【深度】

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

什么是HID原始报告描述符,为什么不能直接用 read()

在Linux环境下,想直接通过 read() 系统调用从 /dev/hidraw0 这类设备文件里读取HID原始报告描述符?这条路走不通。原因在于,原始报告描述符本质上属于设备的“身份档案”和“通信协议说明书”,是静态的元信息,而非动态传输的数据流。

内核在设备枚举阶段就已经完成了对描述符的解析和缓存。因此,当你对 /dev/hidrawX 执行 read() 时,获取到的只能是设备实时上报的输入报告数据,永远碰不到那份底层的描述符二进制数据。

那么正确的入口在哪里?答案是 sysfs 文件系统。只有通过它提供的只读属性文件接口,才能触及到这份原始的、未经内核处理的描述符数据。

/sys/class/hidraw/ 定位设备并读取 report_descriptor

每个被内核识别并创建的 hidraw 设备,都会在 sysfs 中拥有一个对应的目录。你需要找到的,正是这个目录下的 device/report_descriptor 文件。其完整路径通常形如 /sys/class/hidraw/hidraw0/device/report_descriptor

这个文件以二进制格式、小端字节序存储着完整的原始报告描述符,其文件大小就是描述符的实际字节长度。

实际操作时,有几个关键点必须注意:

立即学习“C++免费学习笔记(深入)”;

  • 权限是敲门砖:访问该文件需要 root 权限,或者用户属于 hidraw 组。否则,open() 函数会直接返回权限错误(-EACCES)。
  • 只读且不可映射:该文件属性为只读,不支持写入,也不支持 mmap 内存映射。唯一的方式就是传统的 open()read()
  • 动态处理文件大小:读取前,先用 stat() 获取文件大小(st_size)来分配缓冲区是个好习惯。但要小心,在一些较旧的内核版本(例如4.15及之前)中,这个大小可能返回为0。此时就需要采用循环读取的策略,直到 read() 返回0为止。
  • 注意设备热插拔:设备被拔掉后,对应的 sysfs 路径会失效。健壮的程序需要监听内核的 uevent 事件,或者在每次操作前重新枚举 /sys/class/hidraw/ 目录。

下面是一个结合了上述要点的C++17示例代码片段,包含了基本的错误处理:

#include 
#include 
#include 
#include 

std::vector read_report_descriptor(const std::string& hidraw_dev) {
    std::string path = "/sys/class/hidraw/" + hidraw_dev + "/device/report_descriptor";
    int fd = open(path.c_str(), O_RDONLY);
    if (fd == -1) return {};

    struct stat st;
    if (fstat(fd, &st) == 0 && st.st_size > 0) {
        std::vector buf(st.st_size);
        ssize_t n = read(fd, buf.data(), buf.size());
        close(fd);
        if (n > 0) buf.resize(n);
        return buf;
    }

    // fallback: size unknown
    std::vector buf(4096);
    ssize_t n = read(fd, buf.data(), buf.size());
    close(fd);
    if (n > 0) {
        buf.resize(n);
        return buf;
    }
    return {};
}

libudev 动态发现 hidraw 设备并关联到物理路径

在真实的应用场景中,硬编码 hidraw0 这样的设备名是不可靠的。设备节点序号可能变化,更通用的方法是根据设备的供应商ID(VID)、产品ID(PID)或制造商字符串来动态定位目标设备。

这就需要借助 libudev 库的力量。核心思路是遍历 hidraw 子系统下的所有设备,然后向上查找其父设备(通常是 usbbluetooth 子系统),从而提取出关键的识别属性。

关键步骤分解如下:

  • 定位父设备:使用 udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device") 函数,找到对应的USB父设备节点。
  • 提取VID/PID:从找到的USB父设备节点中,读取 idVendoridProduct 属性。注意,它们是以十六进制字符串形式存储的,需要使用 strtol(..., nullptr, 16) 进行转换。
  • 获取关键路径:同时,从 hidraw 设备节点本身获取 devnode(即 /dev/hidraw2 这样的路径)和 syspath(用于拼接出 report_descriptor 文件的完整路径)。
  • 处理非USB设备:并非所有HID设备都通过USB连接,例如蓝牙HID-over-GATT(HOGP)设备。对于这类设备,需要回退到从 hid 子系统的 HID_ID 属性中解析VID/PID,其格式通常为 0003:00001234:00005678,其中后两组数字分别对应VID和PID。

常见坑:内核版本差异与描述符截断问题

Linux内核在不同版本中对 report_descriptor 文件的处理方式存在差异,这是开发中最容易踩坑的地方之一:

  • 内核版本 ≥ 5.3:行为最理想。描述符被完整导出,通过 stat() 获取的文件大小是准确的。
  • 内核版本 4.15 – 5.2:可能出现描述符截断问题。尤其当描述符长度超过1024字节时,read() 可能只返回前1024字节,并且 stat() 得到的 st_size 很可能为0。
  • 内核版本 < 4.15:情况更复杂。某些内核配置下,sysfs 中可能根本不存在 report_descriptor 这个文件(访问返回 ENOENT)。此时,必须换用 ioctl 方式,通过 HIDIOCGRAWINFOHIDIOCGRDESCSIZEHIDIOCGRDESC 这些命令来获取描述符。但请注意,这种方式要求目标 /dev/hidrawX 设备文件已经被打开,并且内核编译时启用了 HID_RAW 支持。

如果你发现读取出来的描述符只有几十个字节,明显不完整,第一步应该检查内核版本,并直接用命令行工具验证:cat /sys/class/hidraw/hidraw0/device/report_descriptor | hexdump -C,看看输出长度是否正常。

一旦确认是内核导致的截断问题,最健壮的解决方案要么是升级内核到较新版本,要么就是转向使用更复杂但功能完整的 ioctl 接口。

最后必须强调,HID报告描述符本身是一种非常紧凑的二进制格式,没有固定的长度头。解析时必须严格按照HID规范逐项(item)进行,任何对标签(tag)的误判或读取越界,都会导致后续解析完全混乱。这就是为什么强烈不建议手动编写解析器,而应该使用像 libhidapi 这样成熟的库。它提供的诸如 hid_get_manufacturer_string() 等接口,也能很好地辅助进行设备的交叉验证工作。

来源:https://www.php.cn/faq/2325346.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

C++如何控制YAML输出时的块模式与流模式_SetMapFormat用法【进阶】
编程语言
C++如何控制YAML输出时的块模式与流模式_SetMapFormat用法【进阶】

C++如何控制YAML输出时的块模式与流模式_SetMapFormat用法【进阶】 YAML-CPP 中 SetMapFormat 不控制块 流模式 首先需要明确一个关键点:SetMapFormat 函数本身并不直接控制YAML文档的块(Block)或流(Flow)显示样式。它的核心功能是调整 st

热心网友
05.06
c++如何处理文件读取过程中的非法UTF8编码_异常容错【避坑】
编程语言
c++如何处理文件读取过程中的非法UTF8编码_异常容错【避坑】

C++文件读取中非法UTF-8编码的异常处理与容错方案【避坑指南】 遇到 std::invalid_argument 或乱码时,先别急着抛出异常 许多C++开发者在控制台看到 std::invalid_argument 异常时,第一反应是文件读取操作本身出现了问题。实际上,这里存在一个普遍的误解:标

热心网友
05.06
C++ std::any_of/all_of/none_of _ 逻辑条件判定算法【详解】
编程语言
C++ std::any_of/all_of/none_of _ 逻辑条件判定算法【详解】

C++ std::any_of all_of none_of:逻辑条件判定算法【详解】 开门见山,先说一个核心结论:std::all_of、std::any_of、std::none_of 这三个算法,绝非简单的“语法糖”。它们看似直观,但其内部的短路求值逻辑、对空容器的特殊处理以及谓词的调用时机,

热心网友
05.06
C++ std::execution并行算法 _ C++17多线程优化sort【干货】
编程语言
C++ std::execution并行算法 _ C++17多线程优化sort【干货】

C++ std::execution并行算法深度解析 | C++17多线程优化sort性能实战指南 你是否认为,只需简单调用 std::sort(std::execution::par, begin, end) 就能让程序性能飙升?现实往往更为复杂。许多开发者发现,代码执行后CPU占用率并未提升,耗

热心网友
05.06
c++如何读取Linux内核生成的Device Tree二进制流【深度】
编程语言
c++如何读取Linux内核生成的Device Tree二进制流【深度】

C++如何读取Linux内核生成的Device Tree二进制流【深度】 Linux用户态如何解析内核加载的dtb文件 Linux内核在启动过程中会加载并解析dtb(设备树二进制)文件,将其转换为内部数据结构(如struct device_node)。一个关键限制是:**用户态程序无法直接访问内核内

热心网友
05.06

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

POE交换机连接设备后频繁重启原因解析
电脑教程
POE交换机连接设备后频繁重启原因解析

Poe交换机带载后重启:是故障,还是系统在“自救”? 不少朋友遇到过这个头疼的问题:PoE交换机一接上设备就重启。其实,这本质上不是设备坏了,而是供电系统一套精密的自我保护机制在起作用。当负载接入的瞬间,如果系统检测到功耗超标、供电不稳等情况,就会主动触发复位,防止硬件受损。这正是IEEE 802

热心网友
05.06
电饼铛选购指南哪款型号性价比最高
电脑教程
电饼铛选购指南哪款型号性价比最高

高性价比电饼铛:精准匹配、扎实可靠、真正省心 挑选一款高性价比的电饼铛,核心其实很明确:功能要精准匹配你的真实需求,材质工艺必须扎实可靠,细节设计能让你每天用着都省心。它追求的绝不是单纯的便宜或者参数漂亮,而是每一分钱都花在刀刃上。比如,2100W级的稳定火力保证了煎烤效率不打折;0氟不粘涂层配合蜂

热心网友
05.06
红米K30 5G动态壁纸不联网可以使用吗
电脑教程
红米K30 5G动态壁纸不联网可以使用吗

红米K30 5G动态壁纸联网机制全解析 关于红米K30 5G的动态壁纸是否需要一直联网,答案是:完全没必要。这玩意儿用起来其实很“懂事”,它只在你第一次上手和偶尔想换新的时候,才需要网络搭把手。 其背后的逻辑很清晰:手机搭载的MIUI系统,把所有酷炫的动态壁纸资源都放在了小米官方的“云端仓库”里。所

热心网友
05.06
vivo Y35手机桌面时间不显示修复方法
电脑教程
vivo Y35手机桌面时间不显示修复方法

vivo Y35桌面时间不显示?别急,这事儿有解 不少vivo Y35用户可能都遇到过这个情况:一觉醒来,或者换个主题之后,主屏幕上那个熟悉的“时间”不见了。先别急着怀疑手机坏了,事实是,超过八成的类似问题,根源其实很简单——时间组件压根没被“请”上桌面,或者相关的自动设置被无意中关闭了。作为一台搭

热心网友
05.06
英雄联盟手游杰斯新皮肤获取方法与实战评测
游戏攻略
英雄联盟手游杰斯新皮肤获取方法与实战评测

英雄联盟手游杰斯新皮肤外观设计酷炫,充满科技感。技能特效以蓝色能量为主,视觉效果震撼且辨识度高。实战中技能清晰、手感流畅,能提升操作自信与战场表现。整体而言,该皮肤在视觉、特效与实战体验上均表现优异,值得玩家入手。

热心网友
05.06