Windows下监听USB设备插拔最直接稳定的方式是使用RegisterDeviceNotification API,需绑定窗口句柄、注册DBT_DEVTYP_DEVICEINTERFACE并过滤GUID_DEVINTERFACE_DISK,通过WM_DEVICECHANGE消息及wParam值(DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE)判断事件类型,并在退出前调用UnregisterDeviceNotification防止资源泄漏。

Windows下用RegisterDeviceNotification监听USB设备事件
Windows提供RegisterDeviceNotification API,是监听USB存储设备(如U盘、移动硬盘)插拔最直接且稳定的方式。它基于窗口消息机制,不依赖轮询或WMI查询,响应快、开销低。
关键点在于:必须有一个窗口句柄(哪怕隐藏窗口),注册时指定DBT_DEVTYP_DEVICEINTERFACE并过滤GUID_DEVINTERFACE_USB_DEVICE或更精确的GUID_DEVINTERFACE_DISK(推荐后者,避免捕获非存储类USB设备)。
- 注册前需调用
SetWindowLongPtr(hwnd, GWLP_WNDPROC, ...)确保能接收WM_DEVICECHANGE消息 - 收到
WM_DEVICECHANGE后,用lParam指向的DEV_BROADCAST_DEVICEINTERFACE*结构体判断是否为USB存储设备:检查dbcc_classguid是否等于GUID_DEVINTERFACE_DISK - 设备插入时
wParam == DBT_DEVICEARRIVAL,拔出时wParam == DBT_DEVICEREMOVECOMPLETE;注意DBT_DEVICEREMOVEPENDING只是提示即将拔出,不可用于执行卸载逻辑 - 必须在程序退出前调用
UnregisterDeviceNotification,否则可能引发GDI资源泄漏
Linux下用udev监听USB存储设备事件
Linux没有统一的“USB存储插拔”API,正确做法是通过udev监听内核事件。直接读/proc/mounts或轮询/sys/block属于事后检测,延迟高、易漏判。
核心是创建udev_monitor,过滤subsystem == "block"且devtype == "disk",再结合ID_BUS=="usb"和ID_TYPE=="disk"属性确认为USB存储设备。
立即学习“C++免费学习笔记(深入)”;
- 初始化需调用
udev_new()、udev_monitor_new_from_netlink(udev, "udev"),然后udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", "disk") - 监听到事件后,用
udev_device_get_property_value(dev, "ID_BUS")判断是否为"usb",避免把NVMe或SATA设备误认为USB设备 - 设备插入时
action == "add",拔出时action == "remove";但注意:"remove"发生于设备已断开物理连接后,文件系统通常早已被内核卸载 - 务必在子线程中阻塞等待事件(
udev_monitor_receive_device),主线程不能被阻塞;同时要处理udev句柄的释放顺序,避免udev_unref早于udev_monitor_unref
macOS下监听USB存储设备需组合I/O Kit和Disk Arbitration
macOS不暴露类似Windows或Linux的简单事件接口。USB存储设备插拔涉及两层:底层硬件接入(I/O Kit)和上层挂载/弹出(Disk Arbitration)。只监听其中一层会漏事件。
推荐组合使用:IONotificationPortCreate + IOServiceAddMatchingNotification监听IOUSBDevice类设备变化,同时用DASessionRef监听DAStorageDeviceWillMount/DAStorageDeviceDidUnmount等磁盘事件。
- I/O Kit部分能捕获物理连接,但无法区分是否为存储设备——需检查
IOUSBDevice的kUSBInterfaceClassMassStorage接口类,或进一步遍历其子服务中的IOSCSIPeripheralDeviceNub - Disk Arbitration部分更可靠:
DADiskRef对象的getBSDName返回diskX名,配合DAVolumeGetPath可拿到挂载路径;但该事件仅在系统完成自动挂载后触发,拔出时也只在用户点击“推出”后才发出 - 两个框架需分别运行在不同Runloop中(
CFRunLoopGetCurrent()),不能共用同一个Runloop,否则可能死锁 - 注意权限:监听USB设备需用户授权(
com.apple.security.device.usbentitlement),沙盒应用默认禁止
跨平台方案为什么通常不推荐
试图用同一套C++代码在Windows/Linux/macOS上监听USB插拔,往往导致逻辑分支混乱、事件语义不一致、资源管理错位。比如Linux的"remove"对应物理断开,而macOS的DAStorageDeviceDidUnmount对应用户操作,Windows的DBT_DEVICEREMOVECOMPLETE则介于两者之间。
- 第三方库如
libusb只管USB协议层,不感知存储设备挂载状态;libudev在macOS不可用;Boost.Process或std::filesystem完全不提供设备事件能力 - 即使封装成统一接口,也无法掩盖底层差异:Windows事件带
HDEVNOTIFY句柄需手动清理,Linux需管理udev引用计数,macOS需协调两个独立的异步回调队列 - 真正需要跨平台的场景,建议改用进程间通知:主程序在各平台用原生方式监听,通过本地socket或共享内存把事件转成统一JSON发给业务模块
实际开发中,最容易被忽略的是事件时序与资源生命周期的耦合——比如在Linux udev回调里直接调用mount命令,可能因内核尚未完成设备初始化而失败;又比如在macOS Disk Arbitration回调里访问已释放的DADiskRef,引发EXC_BAD_ACCESS。这些都不是API用错,而是对底层事件模型的理解偏差。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/126940.html