编程实现监控U盘或者其它移动设备的插入和拔出

Jonesy

发布日期: 2018-11-28 20:55:37 浏览量: 837
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

如果在没有阅读本文之前,可能你会认为编程实现监控U盘或者其它移动设备的插入和拔出,是一个很难的事情,或者是一个很靠近系统底层的事情。其实,这些你完全不用担心,Windows 已经为我们都设计好了。

我们都知道,Windows应用程序都是消息(事件)驱动的,任何一个窗口都能够接收消息,并对该消息做出相应的处理。同样,U盘或者其它移动设备的插入或者拔出也会有相应的消息与之对应,这个消息便是 WM_DEVICECHANGE。顾名思义,这个消息就是设备更改的时候产生的。那么,我们的程序同样可以捕获到这个消息,只要我们对这个消息做出处理就可以了。

现在,我就把这个程序实现的过程整理成文档,分享给大家。

函数介绍

WM_DEVICECHANGE 消息

通知应用程序对设备或计算机的硬件配置进行更改。窗口通过其WindowProc函数接收此消息。

  1. LRESULT CALLBACK WindowProc(
  2. HWND hwnd, // handle to window
  3. UINT uMsg, // WM_DEVICECHANGE
  4. WPARAM wParam, // device-change event
  5. LPARAM lParam // event-specific data
  6. );

参数

  • hwnd:窗口的句柄。

  • uMsg:WM_DEVICECHANGE标识符。

  • wParam:发生的事件。 该参数可以是Dbt.h头文件中的以下值之一:

VALUE MEANING
DBT_CONFIGCHANGECANCELED 更改当前配置(停靠或停靠)的请求已被取消
DBT_CONFIGCHANGED 由于停靠或停靠,当前配置已更改
DBT_CUSTOMEVENT 发生了自定义事件
DBT_DEVICEARRIVAL 已插入设备或介质,现在可以使用
DBT_DEVICEQUERYREMOVE 请求删除设备或介质的权限。 任何应用程序可以拒绝此请求并取消删除
DBT_DEVICEQUERYREMOVEFAILED 删除设备或介质的请求已被取消
DBT_DEVICEREMOVECOMPLETE 已移除设备或介质片
DBT_DEVICEREMOVEPENDING 一个设备或一块介质即将被删除。 不能否认
DBT_DEVICETYPESPECIFIC 发生设备特定事件
DBT_DEVNODES_CHANGED 已将设备添加到系统中或从系统中删除
DBT_QUERYCHANGECONFIG 请求权限更改当前配置(停靠或停靠)
DBT_USERDEFINED 此消息的含义是用户定义的
  • lParam:指向包含事件特定数据的结构的指针。 其格式取决于wParam参数的值。 有关详细信息,请参阅每个事件的文档。

返回值

  • 返回TRUE以授予请求。
  • 返回BROADCAST_QUERY_DENY以拒绝该请求。

DEV_BROADCAST_HDR 结构体

  1. typedef struct _DEV_BROADCAST_HDR {
  2. DWORD dbch_size;
  3. DWORD dbch_devicetype;
  4. DWORD dbch_reserved;
  5. } DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;

成员

  • dbch_size
    这个结构的大小,以字节为单位。
    如果这是用户定义的事件,则该成员必须是此标头的大小,加上_DEV_BROADCAST_USERDEFINED结构中的可变长度数据的大小。

  • dbch_devicetype
    设备类型,用于确定前三个成员之后的事件特定信息。 该成员可以是以下值之一:

VALUE MEANING
DBT_DEVTYP_DEVICEINTERFACE 设备类。 此结构是DEV_BROADCAST_DEVICEINTERFACE结构
DBT_DEVTYP_HANDLE 文件系统句柄。 这个结构是一个DEV_BROADCAST_HANDLE结构
DBT_DEVTYP_OEM OEM或IHV定义的设备类型。 该结构是DEV_BROADCAST_OEM结构
DBT_DEVTYP_PORT 端口设备(串行或并行)。 这个结构是一个DEV_BROADCAST_PORT结构
DBT_DEVTYP_VOLUME 逻辑卷。 这个结构是一个DEV_BROADCAST_VOLUME结构
  • dbch_reserved

    保留。

DEV_BROADCAST_VOLUME 结构体

  1. typedef struct _DEV_BROADCAST_VOLUME {
  2. DWORD dbcv_size;
  3. DWORD dbcv_devicetype;
  4. DWORD dbcv_reserved;
  5. DWORD dbcv_unitmask;
  6. WORD dbcv_flags;
  7. } DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;

成员

  • dbcv_size
    这个结构的大小,以字节为单位。

  • dbcv_devicetype
    设置为DBT_DEVTYP_VOLUME(2)。

  • dbcv_reserved
    保留; 不使用。

  • dbcv_unitmask
    标识一个或多个逻辑单元的逻辑单元掩码。 掩码中的每个位对应于一个逻辑驱动器。 位0表示驱动器A,位1表示驱动器B,依此类推。

  • dbcv_flags
    此参数可以是以下值之一:

VALUE MEANING
DBTF_MEDIA 更改影响驱动器中的介质。 如果未设置,更改将影响物理设备或驱动器
DBTF_NET 指示逻辑卷是一个网络卷

实现原理

由于我们主要是对设备的插入和拔出做操作,所以,只需要对消息回调函数的参数 wParam 进行判断,是否为设备已插入操作 DBT_DEVICEARRIVAL 和 设备已移除操作 DBT_DEVICEREMOVECOMPLETE。然后再重点分析相应操作对应的 lParam 参数里存储的信息数据,从而分析出产生操作设备的盘符。

设备已插入 DBT_DEVICEARRIVAL

首先,当 wParam 为设备已插入操作 DBT_DEVICEARRIVAL的时候,我们就可以知道是有设备已经插入了。

接下来就是要获取设备的盘符,这需要对参数 lParam 进行分析,此时 lParam 则表示指向 DEV_BROADCAST_HDR 结构的指针。由上述的结构体介绍中,我们可以知道,要获取盘符,就首先要判断 DEV_BROADCAST_HDR 结构体中的设备类型 dbch_devicetype 是否为逻辑卷 DBT_DEVTYP_VOLUME。因为其它的消息类型,是不会产生盘符的。只有消息类型为 DBT_DEVTYP_VOLUME 逻辑卷,才会产生盘符。

由上述结构体介绍中知道,当消息类型为 DBT_DEVTYP_VOLUME 逻辑卷的时候, 参数 lParam 实际上是结构体 DEV_BROADCAST_VOLUME。其中,结构体 DEV_BROADCAST_VOLUME 的 dbcv_unitmask 成员标识一个或多个逻辑单元的逻辑单元掩码,掩码中的每个位对应于一个逻辑驱动器。 位0表示驱动器A,位1表示驱动器B,依此类推。所以,我们可以根据 dbcv_unitmask 计算出设备生成的盘符。

设备已移除 DBT_DEVICEREMOVECOMPLETE

首先,当 wParam 为设备已移除操作 DBT_DEVICEREMOVECOMPLETE 的时候,我们就可以知道是有设备已经移除了。

接下来就是要获取移除设备原来的盘符,这需要对参数 lParam 进行分析,此时 lParam 则表示指向 DEV_BROADCAST_HDR 结构的指针。接下来的分析,和上面设备插入时,获取设备盘符的分析是一样的,在此就不重复了。

编程实现

给程序添加 WM_DEVICECHANGE 的消息响应,并声明定义一个处理函数,处理相应的消息。

对于 Windows应用程序 来说,只需要在窗口消息处理函数中,增加消息类型WM_DEVICECHANGE 的判断即可。然后,调用处理函数进程处理。

对于 MFC 程序来说,则需要自定义 WM_DEVICECHANGE 消息响应函数。在主对话框类的头文件中声明处理函数 :

  1. LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam);

然后,再在主对话框类的消息映射列表中,添加 WM_DEVICECHANGE 与消息处理函数的映射:

  1. BEGIN_MESSAGE_MAP(CWM_DEVICECHANGE_MFC_TestDlg, CDialogEx)
  2. …(省略)
  3. ON_MESSAGE(WM_DEVICECHANGE, OnDeviceChange)
  4. …(省略)
  5. END_MESSAGE_MAP()

那么,Windows应用程序 和 MFC 程序对 WM_DEVICECHANGE 消息的消息处理函数定义都是相同的:

  1. LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam)
  2. {
  3. switch (wParam)
  4. {
  5. // 设备已经插入
  6. case DBT_DEVICEARRIVAL:
  7. {
  8. PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
  9. // 逻辑卷
  10. if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
  11. {
  12. // 根据 dbcv_unitmask 计算出设备盘符
  13. PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
  14. DWORD dwDriverMask = lpdbv->dbcv_unitmask;
  15. DWORD dwTemp = 1;
  16. char szDriver[4] = "A:\\";
  17. for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++)
  18. {
  19. if (0 < (dwTemp & dwDriverMask))
  20. {
  21. // 获取设备盘符
  22. ::MessageBox(NULL, szDriver, "设备已插入", MB_OK);
  23. }
  24. // 左移1位, 接着判断下一个盘符
  25. dwTemp = (dwTemp << 1);
  26. }
  27. }
  28. break;
  29. }
  30. // 设备已经移除
  31. case DBT_DEVICEREMOVECOMPLETE:
  32. {
  33. PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
  34. // 逻辑卷
  35. if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
  36. {
  37. // 根据 dbcv_unitmask 计算出设备盘符
  38. PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
  39. DWORD dwDriverMask = lpdbv->dbcv_unitmask;
  40. DWORD dwTemp = 1;
  41. char szDriver[4] = "A:\\";
  42. for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++)
  43. {
  44. if (0 < (dwTemp & dwDriverMask))
  45. {
  46. // 获取设备盘符
  47. ::MessageBox(NULL, szDriver, "设备已移除", MB_OK);
  48. }
  49. // 左移1位, 接着判断下一个盘符
  50. dwTemp = (dwTemp << 1);
  51. }
  52. }
  53. break;
  54. }
  55. default:
  56. break;
  57. }
  58. return 0;
  59. }

程序测试

这是,我们直接运行程序,插入U盘,程序成功弹窗提示有U盘插入,并给出U盘的盘符。

我们把U盘拔出,程序成功弹窗提示有U盘拔出,并给出U盘的盘符。

总结

本文给出了 MFC程序 和 Windows应用程序 的例子,实现监控U盘或其它移动设备的插入和拔出。其中,我们需要注意理解 DEV_BROADCAST_VOLUME 的 dbcv_unitmask 逻辑单元掩码。它是 4字节 32位,它的每一位都对应一个盘符,从 A 开始计数。如果位中的数值为1,则表示设备操作产生的盘符,为 0,则表示没有产生盘符。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download WM_DEVICECHANGE_Test.7z ( 2.59mb, 7次下载 )

发送私信

如果这世界上真有奇迹,那只是努力的另一个名字

12
文章数
9
评论数
eject