两种检测USB设备插入和移除的方法(Ver 2)

    技术2022-05-11  92

    说明: 这篇文章是第二个版本。最初版本发布于 http://blog.csdn.net/jingzhongrong/archive/2007/01/02/1472440.aspx两种自动检测USB设备的添加和移除的方法 最初写这篇文章是因为当时看见一篇文章提出的判断方法是不断检测盘符变化,由于有更好的实现方法,因此写下那篇文章。在论坛上发现有很多提问是关于怎样判断USB插入和移除的,因此,修改一下最初版本的文章并增加一些更详细的描述,增加一些最初版本文章中没有涉及到的内容。       关于下文出现的API函数的具体使用方法请自行查阅MSDN或者上网搜索。     对于不负责任的转载或者直接复制代码的人,我将在文章中合适的位置写入相应的版权信息(在代码中出现时我会考虑到可读性,或者前面已经有该段代码的不完整版本,或者不影响阅读的变量名等等)

    jingzhongrong

    本文将提供如何判断、检测USB设备的插入和移除的操作。并不提供如何卸载USB设备的描述。     下面详细描述两种检测判断方法。     1、这种方法大概的思路就是在一个线程中循环获得当前系统的盘符,然后获取该盘符对应的设备类型,如果是USB设备会返回DRIVE_REMOVABLE(不一定都是USB设备)。 首先我们需要几个API函数来实现:  

    API 函数声明(详细用法请参见 MSDN // 此函数用于获得当前磁盘驱动器盘符的位掩码 DWORD GetLogicalDrives(VOID)          // 在此用于检测驱动器是否已经准备完毕 BOOL GetVolumeInformation( LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize)    // 此函数用于获得分区信息 UINT GetDriveType(LPCTSTR lpRootPathName)

        接下来,我们实现一个用于判断驱动器分区信息的函数,用来检测是否为可移动磁盘。简单的,我们直接调用GetDriveType函数:  

    代码:检测是否为可移动磁盘 int CheckDisk(char *jzrdisk) {  if(GetDriveType(jzrdisk)==DRIVE_REMOVABLE) return 0;    return -1; }

        下面是检测线程的执行函数,考虑到函数GetLogicalDrives返回的是一个位码,我们需要对该返回值进行一些处理才能调用 GetVolumeInformation 函数检查驱动器是否已经准备完毕。由于该位掩码的最低位代表的是 A 盘付,我们只需在循环中简单的使用移位便可:  

    代码:操作 GetLogicalDrives 函数返回的位掩码 DWORD jzr = GetLogicalDrives();  if (jzr!=0) {    for (int i=0;i<26;i++)    // 只判断 26 个盘符     {        if ((jzr & 1)==1)         {            cout<<(char)(‘A’+i)<<” 盘符可用 ”<<endl;         }         jzr = jzr >>1; // 判断下一个盘符     } }

        我们在获得可用的盘符之后,便可以使用 GetVolumeInformation 来检测驱动器是否已经准备完毕可供我们的使用。下面是一个完整的检测函数 detect 的实现。用来判断是否有 USB 设备:  

    代码: detect 函数 int detect() {        char buf[10];    DWORD jzr =GetLogicalDrives();      if (jzr!=0)     {        for (int i=0;i<26;i++)         {            if ((jzr & 1)==1)             {                sprintf(buf,"%c",'A'+i);                  strcat(buf,":/");                 if(!CheckDisk(buf))              {                if(GetVolumeInformation(                      buf,0,0,0,0,0,0,0))                     {                        // 检测到 U 盘并且驱动器已准备就绪                   return 1;                      }                    }              }               jzr = jzr >>1;          }     }     return 0;  }

        我们在线程的执行函数中:  

    代码: threadProc 函数 DWORD WINAPI ThreadProc(LPVOID lpParameter) {    const int jingzhongrongSleeptime = 100;    while(1)    {       if(detect())       {          // 检测到 USB 设备       }       else       {          // 没有检测到       }       Sleep(jingzhongrongSleeptime);    } }

        这样便可以调用CreateThread函数创建一个检测线程了。       2、对于上面的方法,存在很多很多问题,比如性能上的问题。当我们的USB设备插入或移除的时候,不能在第一时间进行判断。因此我推荐使用下面的方法。对比上面第一种实现方法,这个方法不仅实现起来简单,而且由于它是消息驱动的,性能上也比使用while循环的线程好。     要实现这个判断,我们首先应该了解一下这个信息: WM_DEVICECHANGE。在USB设备插入或者移除等操作发生的时候,系统会将此消息分发到系统中的所有顶层窗口,我们只需要设置我们程序的消息处理函数处理该消息便可实现判断。   WM_DEVICECHANGE消息的wParam中有几个我们需要特别注意的值:  

    Message.wParam DBT_DEVICEARRIVAL      0x8000 A device or piece of media has been inserted and is now available.   // 插入事件 DBT_DEVICEQUERYREMOVE 0x8001 Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the removal. DBT_DEVICEREMOVECOMPLETE 0x8004  A device or piece of media has been removed.                           // 移除事件 DBT_DEVICEREMOVEPENDING 0x8003 A device or piece of media is about to be removed. Cannot be denied.

        有了上述的值,我们便可以对消息进行处理(下面的代码在BCB下编译通过,VC平台可能需要进行修改)  

    代码:消息处理 if(Message.Msg == WM_DEVICECHANGE) {    switch(Message.WParam)     {        case DBT_DEVICEARRIVAL:          // 插入        {          ShowMessage("USB 设备插入 ");             ...           break;        }        case DBT_DEVICEREMOVECOMPLETE:         // 移除          ShowMessage("USB 设备移除 ");          break;    } }

        值得注意的是,当网络驱动器设备连接和移除的时候也会触发这个消息。这样对于我们判断是否为USB设备便产生了影响,不过,我们有办法对其进行判断:  

    代码:判断网络设备 PDEV_BROADCAST_VOLUME dbvDev = (PDEV_BROADCAST_VOLUME) Message.LParam; if (dbvDev->dbcv_flags & DBTF_MEDIA) {    ... }

        在WM_DEVICECHANGE消息的lParam参数中保存了设备的相关信息,我们要对设备的类型进行判断,只需要获得DEV_BROADCAST_VOLUME结构中的dbcv_flags的值。 当它的值为DBTF_NET时,那么当前的这个逻辑卷便是网络卷。所以我们在上面代码中判断dbcv_flags的值是否为DBTF_MEDIA,以此判断是否为网络驱动器。   在lParam参数中,我们还应该对这样一个信息感兴趣,这个设备是什么设备,如果我想针对某一个设备进行操作,那么,我们只需要对设备的GUID进行比较,而存放GUID信息的项出现在DEV_BROADCAST_DEVICEINTERFACE结构中的dbcc_classguid。  

    代码:获取设备 GUID DEV_BROADCAST_HDR *lpDevHdr = (DEV_BROADCAST_HDR*) Message.LParam;   if(lpDevHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {      DEV_BROADCAST_DEVICEINTERFACE * jzr =         (DEV_BROADCAST_DEVICEINTERFACE*)Message.LParam;    GUID guid = jzr ->dbcc_classguid; }

      如果只想对指定设备进行操作可以使用RegisterDeviceNotification函数注册,那么你只会受到你指定的设备的WM_DEVICECHANGE消息。   好了,我们已经把如何判断的问题解决了,下面我们只需要设置窗口的消息处理函数便可以对WM_DEVICECHANGE消息进行处理了,在BCB中,我们只要重写窗体类的WndProc虚函数:  

    代码: WndProc 函数 // 在头文件窗体类中添加声明 void __fastcall WndProc(TMessage &Message);   //cpp 实现 void __fastcall TForm1::WndProc(TMessage &Message) {    if(Message.Msg == WM_DEVICECHANGE)   {       switch(Message.WParam)       {           case DBT_DEVICEARRIVAL:                     {             ShowMessage("USB 设备插入 ");             PDEV_BROADCAST_VOLUME jingzhongrong = (PDEV_BROADCAST_VOLUME)Message.LParam;              DWORD vn = jingzhongrong ->dbcv_unitmask;                break;           }           case DBT_DEVICEREMOVECOMPLETE:               ShowMessage("USB 设备移除 ");             break;       }   }    TForm::WndProc(Message);    // 处理其他消息 }

        我们已经将两种方法说明完毕,下面我们使用第二种方法实现一个简单的程序,用来判断U盘中是否含有疑似病毒的文件(这将是基于大部分U盘病毒都是有autorun.in*文件)我们获得U盘插入消息后,取得盘符并对U盘盘符根目录进行查找是否有文件包含“autorun”字符串,并列出包含有该字符串的文件名,提示用户注意。 程序在Windows 2003 Server + Borland Developer C++Builder 2006中调试通过。 下面是程序清单:  

    头文件 Unit1.h //$$---- Form HDR ---- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <windows.h> #include <ExtCtrls.hpp> #include <XPMan.hpp> //--------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components    TMemo *Memo1;   TButton *Button1;   TButton *Button2;   TXPManifest *XPManifest1;   TTrayIcon *TrayIcon1;   void __fastcall Button1Click(TObject *Sender);   void __fastcall Button2Click(TObject *Sender); private: // User declarations    bool __fastcall HasAutoRunInFileName(unsigned long area);   char __fastcall FirstDriveFromMask(ULONG unitmask);   bool started; public: // User declarations    __fastcall TForm1(TComponent* Owner);   void __fastcall WndProc(TMessage &Message); }; //--------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------- #endif

     

    CPP 文件 //$$---- Form CPP ---- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <windows.h> #include <dir.h> #include <io.h> #include <fcntl.h> #include <Dbt.h> //--------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner)  : TForm(Owner),started(false) { } //--------------------------------------------------------------- void __fastcall TForm1::WndProc(TMessage &Message) {   if(started)   {      if(Message.Msg == WM_DEVICECHANGE)      {          switch(Message.WParam)          {              case DBT_DEVICEARRIVAL:              {                PDEV_BROADCAST_VOLUME dbvDev =  (PDEV_BROADCAST_VOLUME)Message.LParam;                 DWORD vn = dbvDev->dbcv_unitmask;                 this->HasAutoRunInFileName(vn);                  break;               }              case DBT_DEVICEREMOVECOMPLETE:                 ShowMessage(“by jingzhongrong”);                break;          }      }   }   TForm::WndProc(Message); } //--------------------------------------------------------------- bool __fastcall TForm1::HasAutoRunInFileName(unsigned long area) {   char s = FirstDriveFromMask(area);   struct ffblk ffblk;   int done;   AnsiString path = AnsiString(s);   path += ":/*autorun*";   done = findfirst(path.c_str(),&ffblk,0);   Memo1->Clear();   while (!done)   {      Memo1->Lines->Add(AnsiString(ffblk.ff_name));      done = findnext(&ffblk);   }   if(Memo1->Lines->Count != 0)   {      MessageBox(Handle," 注意 ,U 盘中含有包含 "autorun" 字样的文件 , 可能是病毒请不要直接双击打开该 U ","jzrUSBAutoRunScan",MB_OK | MB_ICONHAND);      return true;   }   return false; } //--------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) {   started = true;  // 开始监视    Button2->Enabled = true;   Button1->Enabled = false; } //--------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) {   started = false;  // 停止监视    Button2->Enabled = false;   Button1->Enabled = true; } //--------------------------------------------------------------- char __fastcall TForm1::FirstDriveFromMask(ULONG unitmask) {    char i;    for (i = 0; i < 26; ++i)    {        if (unitmask & 0x1)           break;        unitmask = unitmask >> 1;    }   return (i + 'A'); } //---------------------------------------------------------------  

      本文到此结束,写于2007年9月29日星期六,jingzhongrong ,广州

     


    最新回复(0)