捕获所有USB设备插入消息

0x00 说明

  最先在网上copy的代码,能捕获U盘插入的消息,但不能捕获其他USB设备的消息,且无法获取U盘dbcc_name,多次询问度娘后终于找到正确的姿势,通过RegisterDeviceNotification注册DBT_DEVTYP_DEVICEINTERFACE,获取所有USB类设备的通知。

0x01 WM_DEVICECHANGE

  查询MSDN[WM_DEVICECHANGE message],当设备或电脑的硬件配置发生改变时,会给每一个顶层窗口发通知,窗口通过WindowPro接收这个消息。WindowPro中的wParam参数告诉我们是具体是发生了什么事件,即设备或电脑的硬件配置发生了怎样的变化;lParam参数包含了一些设备具体的信息(wParam不同,对应的lParam也有一点的变化)。如果有U盘插入(这里为什么说是U盘而不是USB设备将会在后面进行说明),wParam=DBT_DEVICEARRIVAL,lParam中包含了U盘的盘符信息。
  有了上述信息,我们可自己创建一个顶层窗口来接WM_DEVICECHANGE消息。首先定义一个WindowPro函数,当message参数为WM_DEVICECHANGE,wParam参数为DBT_DEVICEARRIVAL时,即表示有U盘插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LRESULT CALLBACK UsbProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DEVICECHANGE:
if(wParam == DBT_DEVICEARRIVAL) //设备激活
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
DEV_BROADCAST_HDR* pHdr = (DEV_BROADCAST_HDR*)lParam;
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
PDEV_BROADCAST_DEVICEINTERFACE pdinfo = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
//pdinfo->dbcc_name
TCHAR szMsg[80],Disk;
Disk = FirstDriveFromMask(lpdbv ->dbcv_unitmask);
wsprintf (szMsg, L"Drive %c: Media has arrived./n",Disk);
}
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
return 0;
}

  定义个一个窗口类,将窗口的处理函数定义为UsbProc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;// 窗口类型
wndclass.lpfnWndProc = UsbProc ;//窗口处理函数
wndclass.cbClsExtra = 0 ;//窗口扩展
wndclass.cbWndExtra = 0 ;//窗口实例扩展
wndclass.hInstance = hModule ;//实例句柄
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;//窗口的最小化图标
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;//窗口鼠标光标
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;//窗口背景色
wndclass.lpszMenuName = NULL ;//窗口菜单
wndclass.lpszClassName = szAppName ;// 窗口类名
//创建自定义的窗口类时,在使用该窗口类前必须注册该窗口类
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
//创建窗口用来接收消息
hwnd = CreateWindow (szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hModule, // program instance handle
NULL); // creation parameters

0x03 Register DBT_DEVTYP_DEVICEINTERFACE

  通过上面的代码,能够捕获U盘插入的消息,但却无法捕获到其他类型的USB设备。经过调试,发现只有当pHdr->dbch_devicetype=DBT_DEVTYP_VOLUME时,才会接收到通知。所以上面的代码中用PDEV_BROADCAST_DEVICEINTERFACE结构去格式化lParam就会得到错误的dbcc_classguid和dbcc_name,而用PDEV_BROADCAST_VOLUME则可以获取正确的盘符信息(dbcv_unitmask)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _DEV_BROADCAST_HDR {
DWORD dbch_size;
DWORD dbch_devicetype;
DWORD dbch_reserved;
} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;
typedef struct _DEV_BROADCAST_VOLUME {
DWORD dbcv_size;
DWORD dbcv_devicetype;
DWORD dbcv_reserved;
DWORD dbcv_unitmask;
WORD dbcv_flags;
} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;
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;

  MSDN[RegisterDeviceNotification]中Remarks部分说明了顶层窗口会收到basic notification,其中就包括了DBT_DEVTYP_VOLUME和DBT_DEVTYP_PORT,其他类型的通知则需要通过RegisterDeviceNotification来注册。RegisterDeviceNotification中第二个参数填入DEV_BROADCAST_DEVICEINTERFACE结构,dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE、dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE,也可以根据需要换成他常用设备接口类GUID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
#define DBT_DEVTYP_OEM 0x00000000 // oem-defined device type
#define DBT_DEVTYP_DEVNODE 0x00000001 // devnode number
#define DBT_DEVTYP_VOLUME 0x00000002 // logical volume
#define DBT_DEVTYP_PORT 0x00000003 // serial, parallel
#define DBT_DEVTYP_NET 0x00000004 // network resource
#if(WINVER >= 0x040A)
#define DBT_DEVTYP_DEVICEINTERFACE 0x00000005 // device interface class
#define DBT_DEVTYP_HANDLE 0x00000006 // file system handle
#if(WINVER >= _WIN32_WINNT_WIN7)
#define DBT_DEVTYP_DEVINST 0x00000007 // device instance
*/
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(NotificationFilter);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_reserved = 0;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
HDEVNOTIFY hDevNotify = RegisterDeviceNotification(hwnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
...
UnregisterDeviceNotification(hDevNotify);
...

  修改WindowPro函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LRESULT CALLBACK UsbProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DEVICECHANGE:
if(wParam == DBT_DEVICEARRIVAL) //设备激活
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
DEV_BROADCAST_HDR* pHdr = (DEV_BROADCAST_HDR*)lParam;
PDEV_BROADCAST_DEVICEINTERFACE pdinfo = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
printf("GUID:%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",pdinfo->dbcc_classguid.Data1,pdinfo->dbcc_classguid.Data2,pdinfo->dbcc_classguid.Data3,pdinfo->dbcc_classguid.Data4[0],pdinfo->dbcc_classguid.Data4[1],pdinfo->dbcc_classguid.Data4[2],pdinfo->dbcc_classguid.Data4[3],pdinfo->dbcc_classguid.Data4[4],pdinfo->dbcc_classguid.Data4[5],pdinfo->dbcc_classguid.Data4[6],pdinfo->dbcc_classguid.Data4[7]);
printf("dbcc_name:%s",pdinfo->dbcc_name);
}
}
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
return 0;
}

参考资料
CSDN-关于u盘识别(OnDeviceChange方式)的几个问题,请教!
新浪博客-利用服务获取USB设备插拨事件以及获取设备硬件信息