检测USB设备插入和拔出

由于工作需要,前段时间一直在找关于在C#中如何检测USB设备的资料,其实C#中使用的函数也是引用的操作系统提供的API函数,还不如在C++中写方便,于是自己简单的写了一个检测USB设备插入和拔出的程序。

程序写完之后,将USB光驱、移动硬盘和优盘插入和拔出都是可以检测到的,就是有些内存卡插入的时候检测不到,于是又接着找资料,发现检测读卡器需要另外的方法。在本文中将分为两部分来解释这些是怎么实现的。其实和网上的其他代码示例是差不多的,调用的函数也都是一样的。本文的目的只是将学到的东西记录下来。
1)检测USB光驱、移动硬盘和优盘插入和拔出
在C++窗口类中主要用到了两个函数和处理两个主要消息。
两个函数是RegisterDeviceNotification和UnregisterDeviceNotification。一个消息是指WM_DEVICECHANGE和 DBT_DEVICEREMOVECOMPLETE
在MSDN中对WM_DEVICECHANGE的描述是:Notifies an application of a change to the hardware configuration of a device or the computer,翻译过来就是当设备和计算机有硬件配置变化时通知一个应用程序。并不是每个程序天生就可以收到这个消息,程序如果想获得这个消息必须在程序启动的时候调用RegisterDeviceNotification来进行注册,如果函数执行成功,则有硬件变化时操作系统就会发消息通知该程序。程序结束时需要调用UnregisterDeviceNotification函数撤销注册。
RegisterDeviceNotification的函数原型如下:

HDEVNOTIFY WINAPI RegisterDeviceNotification(
_In_ HANDLE hRecipient,
_In_ LPVOID NotificationFilter,
_In_ DWORD Flags
);

其中hRecipient是当前窗口的窗口句柄(如果是服务的话则是服务句柄);
NotificationFilter是通用的数据结构指针,这些数据结构的开头三项都是下面的结构的成员:
typedef struct _DEV_BROADCAST_HDR {
DWORD dbch_size; // 当前结构的总的大小,按字节算
DWORD dbch_devicetype; // 指定哪些设备有变动时需要通知程序,具体类型可以查MSDN,每种类型的
			       // 数据结构都不一样
  DWORD dbch_reserved;         // 保留位,没什么用
}
DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;
Flags是个标志,用于指定第一个参数的类型,它的取值有DEVICE_NOTIFY_WINDOW_HANDLE(句柄是窗口句柄)、DEVICE_NOTIFY_SERVICE_HANDLE(句柄是服务句柄),如果第二个参数中的dbch_devicetype类型指定成DBT_DEVTYP_DEVICEINTERFACE,则Flags还可以取DEVICE_NOTIFY_ALL_INTERFACE_CLASSES,这个标志的意思是不需要过滤消息,所有的设备变动都通知程序。
我们在例子中设置 dbch_devicetype的值为 DBT_DEVTYP_DEVICEINTERFACE。
如果调用 RegisterDeviceNotification函数成功,则函数返回一个设备通知句柄,当程序结束时将该句柄传递给 UnregisterDeviceNotification函数用于撤销通知。
接下来就是处理 WM_DEVICECHANGE消息,在C++中已经有封装好的映射函数用于处理这个消息,只需要在 BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加一个ON_WM_DEVICECHANGE()就可以了,剩下的就是在窗口类的头文件中添加afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD_PTR dwData),并实现这个函数就可以了。
这个函数的参数意义如下:
nEventType指定设备的消息类型,它可以取下面的值:
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  这些消息的意义可以查询MSDN,我们的程序中主 要使用DBT_DEVICEARRIVAL和DBT_DEVICEREMOVECOMPLETE,前一个事件发生在设备插入后,后一个事件发生在设备拔出后。
第二个参数dwData是一个通用数据结构指针,这些结构都是以 DEV_BROADCAST_HDR结构开始的,在这个结构的第二项中 dbch_devicetype 指定是什么类型的设备,每种类型的数据结构都不一样,只有确定了具体的设备才能确定 dwData指向的 具体的数据类型。本程序中我们只针对 DBT_DEVTYP_VOLUME(设备插入后会才操作系统中出现盘符)类型,该类型设备对应的数据结构如下:
typedef struct _DEV_BROADCAST_VOLUME {
DWORD dbcv_size;
DWORD dbcv_devicetype;
DWORD dbcv_reserved;
DWORD dbcv_unitmask;
WORD  dbcv_flags;
} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;
这个结构中可以从 dbcv_unitmask中获取设备对应的逻辑盘符,从 dbcv_flags中获取设备的大致类型,比如U盘插入的时候 dbcv_flags为0,光盘为1, dbcv_flags值为2时则是与网络卷标相关。
DBT_DEVICEREMOVECOMPLETE与DBT_DEVICEARRIVAL类似,就不介绍了,MSDN中有示例程序。

2)检测存储卡插入和拔出
最初的程序写完之后,用优盘、移动硬盘做试验都没有问题,但是当试验读卡器和存储卡时出现了问题,这里我用的读卡器是插入读卡器后即使不插入存储卡,操作系统中也会出现逻辑盘符,只是不可用,当读卡器的某一个接口中插入存储卡后,这个接口对应的盘符变得可用,但是这时候程序也收不到DBT_DEVICEARRIVAL消息了,也就不知道存储卡是否插进来(拔出好像可以检测到)。
为此又是到网上检索资料,最后找到的方法是也是两个函数和两个消息:
两个函数是SHChangeNotifyRegister和SHChangeNotifyDeregister,两个消息是SHCNE_MEDIAINSERTED和SHCNE_MEDIAREMOVED。
首先来看 SHChangeNotifyRegister,它的函数原型如下:
ULONG SHChangeNotifyRegister(
_In_  HWND hwnd,
int fSources,
LONG fEvents,
UINT wMsg,
int cEntries,
_In_  const SHChangeNotifyEntry *pshcne
);
其中hwnd是当前窗口程序句柄;
fSources指定当前窗口需要获得哪些类型的消息通知,有很多种消息类型,我们需要的是SHCNE_DISKEVENTS;
fEvents指定需要获取的具体的消息,上一个参数仅是一个大类,这个参数就是来指定子类消息;
wMsg是一个自定义消息,当操作系统有我们需要的消息发生时,操作系统会发送wMsg指定的消息来通知我们的程序,这里定义我们的消息为WM_USER_MEDIACHANGED;
cEntries是说明下一个参数,也就是一个数组的元素个数,这个值MSDN中说必须要设置成1;
pshcne这个结构我也不太清楚,直接是抄的网上的代码。
    这个函数调用成功后会返回一个句柄,调用函数 SHChangeNotifyDeregister会用到 该句柄。
接下来就是处理 WM_USER_MEDIACHANGED消息,需要在 BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加一个
ON_MESSAGE(WM_USER_MEDIACHANGED,&CGetDeviceInfoDlg::OnMediaChanged) 就可以了,剩下的就是在窗口类的头文件中添加afx_msg LRESULT OnMediaChanged(WPARAM,LPARAM),并实现这个函数就可以了。
OnMediaChanged函数的第二个参数指定了Windows消息类型,也就是对应的 SHChangeNotifyRegister函数中的fEvents指定的消息类型。由于我们仅仅是需要知道是否有设备插入或者拔出,所以只对 LPARAM进行判断就可以了,(第一个参数 WPARAM 是SHNOTIFYSTRUCT指针,具体意义可以查询MSDN ):

LRESULT CGetDeviceInfoDlg::OnMediaChanged(WPARAM wParam, LPARAM lParam)
{
SHNOTIFYSTRUCT *shns=(SHNOTIFYSTRUCT*)wParam;
CString strPath,strMsg;

switch (lParam)
{
case SHCNE_MEDIAINSERTED:
this->SetDlgItemText(IDC_EDIT_SOURCE,"SHCNE_MEDIAINSERTED");
strPath =GetPathFromPIDL(shns->dwItem1);

if (!strPath.IsEmpty())
{
strMsg.Format("Media inserted into %s",strPath);
this->SetDlgItemText(IDC_EDIT_MSG,strMsg);
}
break;

case SHCNE_MEDIAREMOVED:
this->SetDlgItemText(IDC_EDIT_SOURCE,"SHCNE_MEDIAREMOVED");
strPath=GetPathFromPIDL(shns->dwItem1);
if (!strPath.IsEmpty())
{
strMsg.Format("Media removed from %s",strPath);
this->SetDlgItemText(IDC_EDIT_MSG,strMsg);
}
break;
}

return NULL;

}

3)一些说明
上述例子都是在窗口程序中实现的,除了在窗口程序中获取设备插入、拔出信息,还可以在windows服务中获取这些信息,但是在windows服务中并不能获取到设备对应的逻辑盘符,查询资料知道原来逻辑盘符是用户相关的,一个设备插入后对应的逻辑盘符因用户不同而可能不同,而服务程序与用户无关,所以不能再服务中获取设备的逻辑盘符。

提供的程序是一个比较简陋的C++程序,仅仅是显示设备是插入还是拔出,但是示意效果已经达到了,如果想获取更好的程序,可以到codeproject网站上按照 RegisterDeviceNotification或者 SHChangeNotifyRegister来查找更好的程序。

附件链接如下,名字是 GetDeviceInfo.rar

参考文献:
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
要在Qt应用程序中检测USB设备插入和拨出动作,您可以使用Qt的设备监视器类`QDeviceWatcher`和`QDeviceChangeNotifier`,并结合使用`QStorageInfo`类来获取USB设备的信息。 首先,确保在.pro文件中添加以下模块: ``` QT += core gui ``` 然后,可以使用以下代码来检测USB设备插入和拨出动作: ```cpp #include <QCoreApplication> #include <QDeviceWatcher> #include <QStorageInfo> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QDeviceWatcher deviceWatcher; deviceWatcher.appendEventReceiver(new QDeviceChangeNotifier); QObject::connect(&deviceWatcher, &QDeviceWatcher::deviceAdded, [&](const QString &device){ QStorageInfo storage(device); if (storage.isValid() && storage.isReady() && storage.isRemovable() && storage.isHotpluggable()) { qDebug() << "USB device inserted:" << device; qDebug() << "USB device name:" << storage.displayName(); qDebug() << "USB device size:" << storage.bytesTotal() / (1024 * 1024) << "MB"; // 在这里处理USB设备插入后的操作 } }); QObject::connect(&deviceWatcher, &QDeviceWatcher::deviceRemoved, [&](const QString &device){ QStorageInfo storage(device); if (storage.isValid() && storage.isReady() && storage.isRemovable() && storage.isHotpluggable()) { qDebug() << "USB device removed:" << device; // 在这里处理USB设备拨出后的操作 } }); deviceWatcher.start(); return a.exec(); } ``` 上面的代码中,我们创建了一个`QDeviceWatcher`对象,并连接了`deviceAdded`和`deviceRemoved`信号。当设备插入时,`deviceAdded`信号被触发,当设备拨出时,`deviceRemoved`信号被触发。我们使用`QStorageInfo`类来获取设备的信息,并检查设备是否为有效的、可用的、可移动的和可热插拔的USB设备。 请注意,您可能需要根据实际情况修改代码以适应您的需求。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值