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 ,广州