zoukankan      html  css  js  c++  java
  • windows实时监测热插拔设备的变化(转)

    原文转自 https://blog.csdn.net/windows_nt/article/details/13614849

    序:

        在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 只有顶层窗体的程序才能收到这个消息

    2 仅仅串口、磁盘发生改变,才对每个程序广播这个消息

    的确不错,至少你可以知道移动U盘、移动硬盘、光盘被安装或弹出了,通过DEV_BROADCAST_VOLUME.dbcv_unitmask你也可以获得其对应的盘符。但实际上,你不知道底层处理的是哪个物理设备实际上被安装到了系统中。

    API:RegisterDeviceNotification()

    所以,你不得不调用RegisterDeviceNotification()API来注册其他类型的设备改变,或是你的程序仅仅是一个服务程序、没有顶层窗体的程序。例如:如下的例子是用来注册一个设备类型的接口的:

    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关注的就是你关心的一类设备。

    参考这个blog: Doron Holan's blog

    一个支持即插即用的设备,有2个不同的GUID相关,一个设备接口GUID, 一个是设备类GUID

    设备类GUID:定义了广泛意义上一类设备的GUID,如果你打开设备管理器[我的电脑右键—>设备管理器],默认的是按照“类型”排列的,每一个“类型”就是一个设备类,同时每一个设备类有一个唯一的ID就是设备类GUID。设备GUID定义了此类设备的图标、默认的安全设置、安装属性(例如用户不能手动安装这类设备,而必须通过PNP来遍历),以及其他的设置信息。设备类GUID没有定义对应的I/O接口(请参考术语表),而更像是设备的分组。我认为一个比较好的例子是端口类。串口COM和并口LPT 都是端口类的一部分,但其各有各的I/O接口,而且彼此互不兼容.一个设备仅仅属于一个设备类。我们可以通过设备驱动的INF文件的开头来查看该设备的设备类GUID。

    设备接口GUID:定义了相互关联I/O接口的GUID,每一个接口GUID的具体实例都支持基本的I/O设置。设备接口GUID也是对应的驱动程序基于PNP状态来注册、启用、禁用设备。如果需要,一个设备甚至可以注册多个同样GUID的实例(假使每个都有相同的名字)[注:在实际的程序中,多次插拔USB口,确实会驱出相同的串口,例如port12,port12,port12…],尽管在现实世界中完全不需要这样。一个简单的I/O关联接口是键盘设备,每个键盘设备的接口GUID必须相同。

    可以通过如下的注册表路径来查看当前设备类GUID, 设备接口GUID:

    • \HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlClass
    • \HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlDeviceClasses

     常用设备的接口GUID如下:

    Device Interface Name GUID
    USB Raw Device {a5dcbf10-6530-11d2-901f-00c04fb951ed}
    Disk Device {53f56307-b6bf-11d0-94f2-00a0c91efb8b}
    Network Card {ad498944-762f-11d0-8dcb-00c04fc3358c}
    Human Interface Device (HID) {4d1e55b2-f16f-11cf-88cb-001111000030}
    Palm {784126bf-4190-11d4-b5c2-00c04f687a67}

    解析DEV_BROADCAST_DEVICEINTERFACE
    如下是修改处理捕获对应事件的函数:

    LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
    {
        ....
        ....
        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;
                    UpdateDevice(pDevInf, wParam);
                    break;
        ....
        ....
    }

    从MSDN中,我们知道

    typedef struct _DEV_BROADCAST_DEVICEINTERFACE {
        DWORD dbcc_size;
        DWORD dbcc_devicetype;
        DWORD dbcc_reserved;
        GUID dbcc_classguid;
        TCHAR dbcc_name[1];
    } DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;

    我们似乎可以通过dbcc_name知道那个设备安装到了当前系统。答案是错误的,dbcc_name仅仅是操作系统内部使用来做为ID的,其实不易读的,例如下面的这个dbcc_name:

     

    \?USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}

    • \?USB: USB 意思是这是一个USB设备类
    • Vid_04e8&Pid_053b: Vid/Pid 是硬件ID,由厂商ID和产品ID组成(但这是由设备类指定的,USB设备类使用VID/PID,不同的设备类使用不同的命名约定)
    • 002F9A9828E0F06: 不清楚是怎么生成的,是唯一设备ID
    • {a5dcbf10-6530-11d2-901f-00c04fb951ed}:设备接口类GUID

    现在,我们来解出设备描述信息或是设备别名,有2种办法:

    1 直接读注册表, \HKLMSYSTEMCurrentControlSetEnumUSBVid_04e8&Pid_503b002F9A9828E0F06

    2 使用 SetupDiXxx 系列API

    API:SetupDiXxx()

    Windows定义了一组API,让用户通过编程的办法来获取对应的硬件设备信息。例如,我们可以通过dbcc_name来获得设备描述信息或是设备别名。下面是这个办法都具体步骤:

    1 首先通过SetupDiGetClassDevs()来获得设备信息集 HDEVINFO,这个操作等同于是一个获取目录句柄的过程。

    2 接着使用SetupDiEnumDeviceInfo()来遍历出这个设备信息集内的所有设备,这个操作等同于把目录列表的过程。对于每个遍历出的,我们可以获得SP_DEVINFO_DATA,这个等同于是文件句柄。

    3 在上面的枚举过程中,使用SetupDiGetDeviceInstanceId()来读取每个设备的实例ID,这个操作等同于是读文件的属性,一个设备的实例ID类似这个:”USBVid_04e8&Pid_503b002F9A9828E0F06”,和dbcc_name非常像。

    4 如果设备的实例ID等同于dbcc_name,则通过SetupDiGetDeviceRegistryProperty()来获取设备描述信息或是设备别名信息。

    程序如下:

     

    void CHWDetectDlg::UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam)
    {
        // dbcc_name:
        // \?USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
        // convert to
        // USBVid_04e8&Pid_503b002F9A9828E0F06
        ASSERT(lstrlen(pDevInf->dbcc_name) > 4);
        CString szDevId = pDevInf->dbcc_name+4;
        int idx = szDevId.ReverseFind(_T('#'));
        ASSERT( -1 != idx );
        szDevId.Truncate(idx);
        szDevId.Replace(_T('#'), _T('\'));
        szDevId.MakeUpper();
    
        CString szClass;
        idx = szDevId.Find(_T('\'));
        ASSERT(-1 != idx );
        szClass = szDevId.Left(idx);
    
        // if we are adding device, we only need present devices
        // otherwise, we need all devices
        DWORD dwFlag = DBT_DEVICEARRIVAL != wParam
            ? DIGCF_ALLCLASSES : (DIGCF_ALLCLASSES | DIGCF_PRESENT);
        HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, szClass, NULL, dwFlag);
        if( INVALID_HANDLE_VALUE == hDevInfo )
        {
            AfxMessageBox(CString("SetupDiGetClassDevs(): ")
                + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
            return;
        }
    
        SP_DEVINFO_DATA* pspDevInfoData =
            (SP_DEVINFO_DATA*)HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA));
        pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
        for(int i=0; SetupDiEnumDeviceInfo(hDevInfo,i,pspDevInfoData); i++)
        {
            DWORD DataT ;
            DWORD nSize=0 ;
            TCHAR buf[MAX_PATH];
    
            if ( !SetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize) )
            {
                AfxMessageBox(CString("SetupDiGetDeviceInstanceId(): ")
                    + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
                break;
            }
    
            if ( szDevId == buf )
            {
                // device found
                if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                    SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                    // do nothing
                } else if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                    SPDRP_DEVICEDESC, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                    // do nothing
                } else {
                    lstrcpy(buf, _T("Unknown"));
                }
                // update UI
                // .....
                // .....
                break;
            }
        }
    
        if ( pspDevInfoData ) HeapFree(GetProcessHeap(), 0, pspDevInfoData);
        SetupDiDestroyDeviceInfoList(hDevInfo);
    }
    
    <a href="http://blog.csdn.net/windows_nt" target="_blank">文章来自:http://blog.csdn.net/windows_nt</a>

    禁用设备

    假使你有一个正确的HDEVINFO和SP_DEVINFO_DATA(实际上,我们保持dbcc_name座位树节点的tag,当右键单击某一个节点的时候,可以通过调用SetupDiGetClassDevs和SetupDiEnumDeviceInfo来获得所需东西),按照如下的步骤即可禁用一个设备:

    1 给SP_PROPCHANGE_PARAMS结构体赋上正确的值

    2 把上面赋完值的SP_PROPCHANGE_PARAMS作为参数传入到SetupDiSetClassInstallParams()

    3 调用SetupDiCallClassInstaller(),传递参数DIF_PROPEFRTYCHANGE

    实际上,DIF也是按位做与运算后兼容的,你也可以去传递不同的DIF参数来调用SetupDiSetClassInstallParams()。 更多信息,请参考MSDN”Handling DIF Codes”

     

    SP_PROPCHANGE_PARAMS spPropChangeParams ;
    spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE ;
    spPropChangeParams.Scope = DICS_FLAG_GLOBAL ;
    spPropChangeParams.HwProfile = 0; // current hardware profile
    spPropChangeParams.StateChange = DICS_DISABLE
    
    if( !SetupDiSetClassInstallParams(hDevInfo, &spDevInfoData,
        // note we pass spPropChangeParams as SP_CLASSINSTALL_HEADER
        // but set the size as sizeof(SP_PROPCHANGE_PARAMS)
        (SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(SP_PROPCHANGE_PARAMS)) )
    {
        // handle error
    }
    else if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData))
    {
        // handle error
    }
    else
    {
        // ok, show disable success dialog
        // note, after that, the OS will post DBT_DEVICEREMOVECOMPLETE for the disabled device
    }

    附录:

    我使用这个程序,已经多次测试了USB的无线网卡的,插入、拔出测试。

    局限性:

    1 明显的,必须先运行该程序,才能检测硬件设备。例如:设备在操作系统启动前就已经连接,或者在这个程序运行前的连接都不会被检测。但这个问题,可以通过保存当前系统配置到远程计算机上,等启动完这个程序后再坚持不同的配置来解决

    2 我们可以禁用设备,换而言之这也是我们所有能做到。我们不能访问设备底层控制。 我认为可以通过重新用基于内核的过滤驱动来实现,则可以解决这个问题。

    非常好的文章

    原文:http://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal 

    一个封装的库http://www.codeproject.com/Articles/119168/Hardware-Change-Detection

  • 相关阅读:
    Linnia学习记录
    漫漫考研路
    ENS的学习记录
    KnockoutJS 3.X API 第四章 数据绑定(4) 控制流with绑定
    KnockoutJS 3.X API 第四章 数据绑定(3) 控制流if绑定和ifnot绑定
    KnockoutJS 3.X API 第四章 数据绑定(2) 控制流foreach绑定
    KnockoutJS 3.X API 第四章 数据绑定(1) 文本及样式绑定
    KnockoutJS 3.X API 第三章 计算监控属性(5) 参考手册
    KnockoutJS 3.X API 第三章 计算监控属性(4)Pure computed observables
    KnockoutJS 3.X API 第三章 计算监控属性(3) KO如何实现依赖追踪
  • 原文地址:https://www.cnblogs.com/happykoukou/p/9156650.html
Copyright © 2011-2022 走看看