C# 使用 LibUsbDotNet 实现 USB 设备检测
国庆节回来后的工作内容,基本都在围绕着各种各样的硬件展开,这无疑让本就漫长的 “七天班” ,更加平添了三分枯燥,我甚至在不知不觉中学会了,如何给打印机装上不同尺寸的纸张。华为的 Mate 60 发布以后,人群中此起彼伏地传出 “遥遥领先” 的声音,大概人类总是热衷于评价那些不甚了解的事物。这个现象到了工作中就会变成,总有某些人觉得某件事情特别简单。其实。一切你认为“简单”的东西,背后一定有无数的人们上下求索、苦心孤诣,就像计算机从早期的埃尼阿克(ENIAC)发展到今天的智能手机,你能使用它并不代表它就“简单”,人还是应该对为止的领域保持敬畏和谦逊。回到这篇文章,今天我想和大家聊一聊,我为了解决那些“简单”的问题而做出的尝试。本期的故事主角是我们最熟悉不过的 USB 设备,有道是 “千古兴亡多少事”,且听我娓娓道来。
故事是这样的,基于某些不可抗因素上的考虑,博主需要在程序中集成某厂商的硬件。我猜测,人们觉得这件事情“简单”,或许是看到这个设备有一条 USB 连接线,因为在人们的固有印象中,只要把它接到电脑上就可以正常工作了。事实的确如此,因为你只要考虑串口(SerialPort)、USB 以及这两者间的相互转换即可。当然,这世上的事情圆满者少,遗憾者多,博主在使用过程中发现,厂商的提供的 SDK 存在 Bug,当设备从电脑上拔出后,其 SDK 的初始化函数依然正常返回了,这意味着我们无法在使用设备前“正确”地检测出硬件状态。考虑厂商愿不愿意修复这个 Bug 还是个未知数,博主不得不尝试另辟蹊径。

相信这张图片大家都见过无数次啦,在这里你可以看到操作系统接入的各种设备。以鼠标为例,通过下面这个对话框,我们可以获得这个设备的各种属性信息:

在各种属性信息中,硬件 Id 是最为关键的一组信息,我们可以看到鼠标这个设备的 VID 为 0000,PID 为 3825。其中,VID 是指 Vender ID,即:供应商识别码;PID 是指 Product ID,即:产品识别码。事实上,所有的 USB 设备都有 VID 和 PID,VID 由供应商向 USB-IF 申请获得,而 PID 则由供应商自行指定,计算机正是 VID、PID 以及设备的版本号来决定加载或者安装相应的驱动程序。因此,如果想要判断计算机是否连接了某个 USB 设备,我们可以使用下面的方案:
bool HasUsbDevice(string vid, string pid)
{var query = $"SELECT * FROM Win32_PnPEntity WHERE DeviceID LIKE 'USB%VID_{vid}&PID_{pid}%'";var searcher = new ManagementObjectSearcher(query);var devices = searcher.Get();return devices.Count > 0;
}
需要说明的是,这是通过古老的 WMI 来查询 USB 设备信息,还记得我们前面收集到的 VID 以及 PID 吗?此时,我们需要简单调用一下即可:
if (HasUsbDevice2("0000", "3825") {Console.WriteLine("[WMI]设备已连接");
} else {Console.WriteLine("[WMI]设备未连接");
}
当然,在 .NET 8.0 发布以后,依然固执地抱着这些 Windows 平台的 API 不放,多少有点食古不化的意味。所以,实际工作中我会推荐本文题目中的 LibUsbDotNet 库,除了跨平台方面的考量,这个库的功能要更强大一点,可以做到向 USB 设备发送数据或者从 USB 设备接收数据。下面由我来对这个库的使用进行说明,目前,我们可以从 Github 以及 SourceForge 上下载对应的项目,两者的区别是 Github 上的项目更新一点:
- https://github.com/LibUsbDotNet/LibUsbDotNet
- https://sourceforge.net/projects/libusbdotnet/
下载后是一个可执行文件,我们点击安装即可,它会安装好相关的库以及驱动文件,默认的安装目录为:C:\Program Files\LibUsbDotNet。在安装完成后,它会提示我们进入下面的对话框,这一步的目的是给特定的设备安装 libusb 驱动,因为只有安装了驱动的情况下,接下来的一切才会发生,除非 LibUsbDotNet 会隔空取物。

这里,我们还是选择鼠标这个硬件,你需要重点关注 PID 以及 VID 两个参数,因为这是唯一能区分不同 USB 设备的标识:

最后,点击 “Install” 按钮即可为当前设备安装 libusb 驱动。接下来的事情就变得非常简单啦,我们只需要通过 NuGet 安装 LibUsbDotNet 即可:
bool HasUsbDevice(short vid, short pid)
{var useDeviceFinder = new UsbDeviceFinder(vid, pid);var usbDevice = UsbDevice.OpenUsbDevice(useDeviceFinder);return usbDevice != null;
}
可以注意到,LibUsbDotNet 需要的 VID 以及 PID 都是 short 类型的,所以,相比于 WMI 的方案,它在调用上会存在一点差异:
var verdorId = Convert.ToInt16("0x0000", 16);
var productId = Convert.ToInt16("0x3825", 16);
if (HasUsbDevice(verdorId, productId)) {Console.WriteLine("[LibUsbDotNet]设备已连接");
} else {Console.WriteLine("[LibUsbDotNet]设备未连接");
}
显然,你会注意到,我在原本的 “0000” 和 “3825” 前面都补了 “0x” 这样的字符,这是因为 VID 和 PID 都是 16 位的二进制数,它们都可以简写为 4 位十六进制数,所以,不管是在 Windows 上还是 LibUsbDotNet 提供的软件中,它都是以 4 位十六进制数的简写形式存在的。因此,这里就需要进行先补 “0x” 再做转换的处理。

除了判断 USB 设备是否存在,有时候我们还需要关注 USB 设备的状态变化。例如,插入 USB 设备或者拔出 USB 设备。古人云:世上本无事,庸人自扰之。可这个世界上还就真的有这般无聊的人,动辄喜欢搞拔设备、拔网线这种所谓的深度测试。所以,下面我们来考虑如何处理这种极端的场景。从一开始,博主选择 LibUsbDotNet 这个库,就是看到它提供了 DeviceNotifier 这个类型。不过,在博主后续的尝试中发现,截止到 2.2.29 版本,这个类型已然无迹可寻,而 3.X 版本目前依然出于预发行状态,并且 API 与现在的版本不兼容,所以,这个念头不得不就此作罢。

当然,在觉醒了 WMI 的远古记忆以后,我们会意识到 Windows 下存在着一个大型的数据库,理论上我们只需要查询这个数据库,就可以监听到 USB 设备的状态变化。如图所示,我们会注意到每个硬件的对话框里有一个 “事件” 选项卡,而这些事件最终会在事件查看器里面汇合。在 ChatGPT 以及 wbemtest 的帮助下,我们找到了两个重要的重要的类名:__InstanceCreationEvent、__InstanceDeletionEvent。此时,我们可以编写出下面的代码:
void MonitorUsbDevice()
{// 监听 USB 设备插入var queryInsert = new WqlEventQuery("SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_USBControllerDevice'");var watcherInsert = new ManagementEventWatcher(queryInsert);watcherInsert.EventArrived += (sender, e) =>{// 被插入的逻辑处理var targetInstance = (ManagementBaseObject)e.NewEvent["TargetInstance"];// \\SNOWFLY-PC\root\cimv2:Win32_PnPEntity.DeviceID="HID\\VID_0000&PID_3825\\6&2BE8ADFA&0&0000"var deviceId = targetInstance.Properties["Dependent"].Value.ToString();var device = new ManagementObject(deviceId);var args = new DeviceNotifierEventArgs();// Win32_PnPEntity.DeviceID="HID\\VID_0000&PID_3825\\6&2BE8ADFA&0&0000"args.DeviceId = device.Path.RelativePath.Split("=")[1].Replace("\"", "");args.DevicePath = device.Path.ToString();args.Pid = "0x" + deviceId.Split(new char[] { '&', '\\' }).FirstOrDefault(x => x.StartsWith("PID_")).Replace("PID_", "");args.Vid = "0x" + deviceId.Split(new char[] { '&', '\\' }).FirstOrDefault(x => x.StartsWith("VID_")).Replace("VID_", "");if (!args.DeviceId.StartsWith("USB")) return;Console.WriteLine($"设备已插入 => {JsonConvert.SerializeObject(args)}");};watcherInsert.Start();var queryDelete = new WqlEventQuery("SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_USBControllerDevice'");var watcherDelete = new ManagementEventWatcher(queryDelete);watcherDelete.EventArrived += (sender, e) =>{// 被拔出的逻辑处理var targetInstance = (ManagementBaseObject)e.NewEvent["TargetInstance"];// \\SNOWFLY-PC\root\cimv2:Win32_PnPEntity.DeviceID="HID\\VID_0000&PID_3825\\6&2BE8ADFA&0&0000"var deviceId = targetInstance.Properties["Dependent"].Value.ToString();var device = new ManagementObject(deviceId);var args = new DeviceNotifierEventArgs();// Win32_PnPEntity.DeviceID="HID\\VID_0000&PID_3825\\6&2BE8ADFA&0&0000"args.DeviceId = device.Path.RelativePath.Split("=")[1].Replace("\"", "");args.DevicePath = device.Path.ToString();args.Pid = "0x" + deviceId.Split(new char[] { '&', '\\' }).FirstOrDefault(x => x.StartsWith("PID_")).Replace("PID_", "");args.Vid = "0x" + deviceId.Split(new char[] { '&', '\\' }).FirstOrDefault(x => x.StartsWith("VID_")).Replace("VID_", "");if (!args.DeviceId.StartsWith("USB")) return;Console.WriteLine($"设备已拔出 => {JsonConvert.SerializeObject(args)}");};watcherDelete.Start();
}
理解这段代码基本上没有任何难度,唯一需要说明的地方是,插入或者拔出一个 USB 设备实际上会产生两条消息,它们分别表示的是设备实例与接口实例的创建。这个话听起来或许有些晦涩,可能连微软都不知道它自己在说什么。具体到博主的这个示例中,其规律是两者的 DeviceID 格式不同,一次是 HID ,一个是 USB,因此,我们只需要过滤掉 HID 的那条消息即可。最终,博主实现的效果如下图所示:

有了这个思路,我们就可以在程序启动时对 USB 设备进行监控,一旦发现某个重要的设备被移除,程序就可以及时地做出响应或处理,而不用等到真正要用设备的时候引发异常,我越来越觉得,编程本质就是一群聪明人在千方百计地照顾一个“巨婴”,每次测试同事都说这里或者那里要加一个提示,可即使增加了提示,人们依然无止无休地问你为什么,错误信息不过是程序员自我安慰剂,除了程序员以外没有人会在乎它具体是什么。如果你对此怀疑表示怀疑的话,不妨回去翻翻你写的代码,有多少行是真正的、有用的代码,又有多少代码是为了防呆呢?好了,以上就是这篇博客的全部内容啦,本文完。
相关文章:
C# 使用 LibUsbDotNet 实现 USB 设备检测
国庆节回来后的工作内容,基本都在围绕着各种各样的硬件展开,这无疑让本就漫长的 “七天班” ,更加平添了三分枯燥,我甚至在不知不觉中学会了,如何给打印机装上不同尺寸的纸张。华为的 Mate 60 发布以后,人群…...
系统安全分析与设计
系统安全分析与设计(2分) 内容提要 对称加密与非对称加密 加密技术与认证技术 加密技术(只能防止第三方窃听) 讲解地址:对称加密与非对称加密_哔哩哔哩_bilibili 认证技术 骚戴理解:数字签名是用私钥签名…...
UE4 AI群集实现
逻辑就不用说了,就是计算对应图形位置让每个Pawn移动到该位置 因为有时候AI与AI会卡住 所以加上这个Bool为true,以及设置两个AI之间至少隔的距离,设置在一个合理的参数即可 有时候AI群集,AI与AI会比较紧密,可以将Caps…...
机器学习---CNN(创建和训练一个卷积神经网络并评估其性能)下
import numpy as np import matplotlib.pyplot as plt from cnn_operations import cnn_operations as cnn_opr convolutional_neural_network模块: 1. 卷积神经网络类 def __init__(self):# 网络的层数self.n_layers 0# list,网络中的各层self.layers…...
2021-arxiv-Prefix-Tuning- Optimizing Continuous Prompts for Generation
2021-arxiv-Prefix-Tuning- Optimizing Continuous Prompts for Generation Paper:https://arxiv.org/pdf/2101.00190.pdf Code:https://github.com/XiangLi1999/PrefixTuning 前缀调优:优化生成的连续提示 prefix-tunning 的基本思想也是想…...
使用CMakeLists.txt简化项目构建过程
在软件开发过程中,项目的构建是一个不可避免的环节。而随着项目规模的增大,手动管理编译过程变得越来越繁琐。为了简化构建流程并实现跨平台支持,CMake作为一种流行的构建系统被广泛采用。本文将介绍CMakeLists.txt文件的结构,以及…...
构建并训练简单的CNN
1. 构建并训练深度神经网络模型 1.1 准备数据集 本次使用自己生成的一些数据,如下生成代码: # 准备数据集 # 此处自己生成一些原始的数据点 dataset_X=np.linspace(-10,10,100) dataset_y=2*np.square(dataset_X)+7...
Axi_Lite接口的IP核与地址与缓冲与AxiGP0
AXI Interconnect互连内核将一个或多个 AXI 内存映射主设备连接到一个或多个内存映射从设备。 AXI_GP 接口 AXI_GP 接口是直接连接主机互联和从机互联的端口的。 AXI_HP 接口具有一个 1kB 的数据 FIFO 来做缓冲 [4],但是 AXI_GP 接口与它不同,没…...
maven以及配置
oss oss配置 <!--oss--> <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.6.0</version></dependency> lombok <!--lombok--><dependency><gro…...
系统可靠性分析与设计
系统可靠性分析与设计 内容提要 可靠性相关概念 骚戴理解:计算机系统的可靠性和可用性不是完全相同的概念,尽管它们在某些方面有重叠之处。 可靠性指的是计算机系统在特定时间段内正常运行的能力,即系统在面对各种故障或意外情况时能够继续…...
热点不热!如何修复笔记本电脑未连接到移动热点的问题
当你远离常规Wi-Fi时,移动热点是让你的笔记本电脑上网的关键,但当它没有按计划运行时,你会怎么办?以下是Windows笔记本电脑无法连接到移动热点时的几种修复方法。 为什么我的笔记本电脑没有连接到我的热点 由于你的笔记本电脑正试图连接到另一个有限制和可能存在问题的设…...
2024年申报国自然项目基金撰写及技巧
随着社会经济发展和科技进步,基金项目对创新性的要求越来越高。申请人需要提出独特且有前瞻性的研究问题,具备突破性的科学思路和方法。因此,基金项目申请往往需要进行跨学科的技术融合。申请人需要与不同领域结合,形成多学科交叉…...
springMvc的简介
1.说说你对 SpringMVC 的理解 SpringMVC 是基于对java EE servlet的封装,它是轻量级MVC 框架,它是Spring下的一个模块,我们通过编写一个方法实现对应的handler,一个servlet 请求 2.什么是MVC模式? MVC全名是Model V…...
Unity Profiler 详细解析(一)
Overview: . Profiler简介 . Profiler各模块介绍 . 各平台下Profiler的使用 . 基于Profiler的优化定位 . Profiler的主要参数详解 . Profiler案例 Profiler简介 Profiler 是Unity中分析性能开销的工具 • 各种开销一览无遗 • 可跨平台使用(Web、PC、iOS、Android、…...
BMS电池管理系统理论基础
目录 1 、锂离子电池特性分析 1.1、 锂离子电池工作原理 1.2 锂离子电池特性 (1)容量特性...
BLUE引擎变量数据分析
今天跟大家说一下BLUE引擎的变量运用,以及使用中的小细节。大家在使用变量的时候,自定义变量不要以P、G、M、I、D、N、A开头。 变量与变量之间的常用格式: SMALL M88 <$STR(G88)> ;检测私人变量M88,是否小于全局变量G88 LARGE M88 &l…...
第三章 C++的循环结构
系列文章目录 第一章 C的输入第二章 C的输出 文章目录 系列文章目录前言一、个人名片二、while三、do-while四、for总结 前言 今天来学循环结构! 一、个人名片 个人主页:睡觉觉觉得 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评…...
基于卷积优化优化的BP神经网络(分类应用) - 附代码
基于卷积优化优化的BP神经网络(分类应用) - 附代码 文章目录 基于卷积优化优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.卷积优化优化BP神经网络3.1 BP神经网络参数设置3.2 卷积优化算法应用 4.测试结果…...
【MATLAB源码-第50期】基于simulink的BPSK调制解调仿真,输出误码率。
操作环境: MATLAB 2022a 1、算法描述 1. Bernoulli Binary: 这个模块生成伯努利二进制随机数,即0或1。这些数字表示要传输的原始数字信息。 2. Unipolar to Bipolar Converter: 此模块将伯努利二进制数据从0和1转换为-1和1,这是BPSK调制的标…...
【Acwing166】数独(dfs+剪枝+位运算)超级详细题解!
本题思路来源于acwing算法提高课 题目描述 看本文需要准备的知识 1.dfs算法基本思想 2.位运算基础 3.对剪枝这个名词的大概了解 剪枝优化位运算优化 常见四种剪枝策略 首先考虑这道题的搜索顺序,很明显,可以随意选择一个空格子,分支为这…...
信托资金流向与交易对手辨析:钱给了谁,谁就是交易对手吗?
目录 一、 核心误区:资金流向 ≠ 交易对手 二、 谁才是真正的“交易对手”? 三、 如何一眼识别真正的交易对手? 总结 在信托业务和资产管理领域,很多初学者甚至从业者容易产生一个误区:认为信托公司把钱打给谁&…...
OBS多平台直播终极指南:免费开源工具实现一键同步推流
OBS多平台直播终极指南:免费开源工具实现一键同步推流 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 想要在多个直播平台同时推送高质量内容?OBS Multi RTMP插件…...
空气质量指数背后的科学:从污染物浓度到健康影响的完整解读
空气质量指数背后的科学:从污染物浓度到健康影响的完整解读 清晨推开窗户,你是否曾因灰蒙蒙的天空而犹豫今天的晨跑计划?天气预报中那个看似简单的AQI数值,实则凝结了环境科学家们数十年的研究成果。这个介于0到500之间的数字&am…...
从传统后端到阿里大模型应用层:我的两年转型之路,收藏这份进阶指南!
本文分享了一位传统后端开发转向大模型应用层的成长历程。作者通过五年学习,从初识LLM API使用,到深入理解模型原理,再到掌握RAG技术和流式编程,最终成功获得字节超30%涨幅的Agent开发岗位。文章强调提示词写作、模型微调、开源项…...
告别网盘限速!八大平台免费直链下载助手完整指南
告别网盘限速!八大平台免费直链下载助手完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 …...
OpenClaw 大结局——接入个人微信厮
本课概览 Microsoft Agent Framework (MAF) 提供了一套强大的 Workflow(工作流) 框架,用于编排和协调多个智能体(Agent)或处理组件的执行流程。 本课将以通俗易懂的方式,帮助你理解 MAF Workflow 的核心概念…...
GLM-OCR快速入门:从上传图片到获取文字,全程只需2分钟
GLM-OCR快速入门:从上传图片到获取文字,全程只需2分钟 1. 为什么选择GLM-OCR? 在日常工作和学习中,我们经常需要将图片中的文字提取出来。传统OCR工具要么识别率不高,要么操作复杂。GLM-OCR作为一款轻量级专业级多模…...
“advisor复合电源模型:采用新增构型方法修改的优越性”
advisor复合电源模型。 采用新增构型方法修改的复合电源模型,比advisor书上那种在纯电基础上修改好很多,因为保留了自带的纯电模型,所以可方便比较有无超级电容的影响。 模型运行完全正常 无报错。搞过混合动力系统仿真的朋友都知道…...
收藏!逛遍AI论坛发现:京东AI岗薪资竟碾压多家大厂?小白/程序员必看
最近沉迷逛各类AI技术论坛,每天雷打不动翻几十个帖子,其中最吸引我的,就是程序员们分享的AI求职经验帖——尤其是那种薪资爆料、offer选择类的内容,既能看个热闹,更能摸清当下AI岗位的真实市场行情,比单纯看…...
ESLyric-LyricsSource:解锁Foobar2000高级逐字歌词的完整指南
ESLyric-LyricsSource:解锁Foobar2000高级逐字歌词的完整指南 【免费下载链接】ESLyric-LyricsSource Advanced lyrics source for ESLyric in foobar2000 项目地址: https://gitcode.com/gh_mirrors/es/ESLyric-LyricsSource 你是否曾经在使用Foobar2000时&…...
