日期:2014-05-16  浏览次数:21146 次

windows用户模式下检测即插即用设备的变化

序:
    在21世纪,这个信息时代,热插拔设备是一个巨大的安全隐患。在这个篇文章中,我将介绍一种在用户模式下检测即插即用设备的方法。比如,在系统中插入一个usb设备,ipod,无线网卡等等,都可以在用户模式下检测到,并决定开启或关闭新插入的设备。并且,在文章结尾,我将介绍一下这种方法的优点,以及限制。
    怎样检测硬件改变呢?
    事实上,windows操作系统在检测到硬件变化时,会发送一个WM_DEVICECHANGE硬件change消息。因此,我们要做的就是在我们的程序中添加WM_DEVICECHANGE的消息响应。

代码事例如下:

BEGIN_MESSAGE_MAP(CHWDetectDlg, CDialog)zai
    // ... other handlers
    ON_MESSAGE(WM_DEVICECHANGE, OnMyDeviceChange)
END_MESSAGE_MAP()

LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
    // for more information, see MSDN help of WM_DEVICECHANGE
    // this part should not be very difficult to understand
    if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        switch( pHdr->dbch_devicetype ) {
            case DBT_DEVTYP_DEVICEINTERFACE:
                PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_HANDLE:
                PDEV_BROADCAST_HANDLE pDevHnd = (PDEV_BROADCAST_HANDLE)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_OEM:
                PDEV_BROADCAST_OEM pDevOem = (PDEV_BROADCAST_OEM)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_PORT:
                PDEV_BROADCAST_PORT pDevPort = (PDEV_BROADCAST_PORT)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_VOLUME:
                PDEV_BROADCAST_VOLUME pDevVolume = (PDEV_BROADCAST_VOLUME)pHdr;
                // do something...
                break;
        }
    }
    return 0;
}

    然而,在默认情况下,windows操作系统只是发送WM_DEVICECHANGE消息给同时具备以下条件的程序(以下是原文):
1.All applications with a top-level window, and
2.Only upon port and volume change.
    这样的设计其实并不坏,至少操作系统给了你提示,在你的应用程序中检测设备加载与卸载的提示。你可以在设备变更后通过DEV_BROADCAST_VOLUME.dbcv_unitmask捕捉设备消息。缺点是当你获得提示时,你的系统已经加载或卸载了设备。
RegisterDeviceNotification()
    想在其他类型的设备改变时获得通知,或者当你的程序不是top-level窗口时,你可以通过调用RegisterDeviceNotification() API来实现。
比如,当你想要获得upon interface改变的通知时,你可以这样做:

DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
// assume we want to be notified with USBSTOR
// to get notified with all interface on XP or above
// ORed 3rd param with DEVICE_NOTIFY_ALL_INTERFACE_CLASSES and dbcc_classguid will be ignored
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USBSTOR;
HDEVNOTIFY hDevNotify = RegisterDeviceNotification(this->GetSafeHwnd(),
    amp;NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
if( !hDevNotify ) {
    // error handling...
    return FALSE;
}

在看一下第8行NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USBSTOR;
即插即用设备由两部分组成:设备接口GUID和设备类GUID。
设备类GUID是一个广泛的设备类概念,打开“设备管理器”,在“查看”菜单中选择“依类型排序设备”,你所看到的每一个子树节点,都是一个设备类。
一个设备类GUID定义了设备类的图标,默认安全设置,安装属性(如用户不能手动安装这个类的实例,它必须列举PNP型),和其他设置。
设备类GUID并没有定义一个I/O接口,来把它作为一个分组的依据。我认为一个好的例子就是端口类。
COM设备和并口设备都是端口类的一部分,然而它们各自都有自己的I/O接口,并且互不兼容。
一个设备只能属于一个设备类。在INF文件的顶部你看到GUID,就是设备类GUID。

一个设备接口GUID定义了一组特定的I/O接口规范。
规范要求每个设备接口GUID的实例都必须支持相同的I/O接口。
每个驱动程序都要注册和启用/禁用设备接口GUID基于PnP状态。
一个设备可以注册多个设备接口GUID,它不局限于一个设备接口GUID。
如果需要,一个设备可以注册多个拥有相同设备接口GUID的事例。一个简单的I/O接口规范是键盘设备接口,键盘接口GUID的每个实例都必须支持原子输入线程。

You can see the current list of device classes and device interface classes at the following registries:

  • \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
  • \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses