简介
在窗口程序中,我们可以通过 WM_DEVICECHANGE
消息获取到接入的U盘设备,
某些情况下,我们需要在服务中去监控U盘设备接入,并获取对应盘符,来进行其他操作。
工作原理
创建服务的本身框架,这里不再复述,主要介绍如何监控U盘相关。
在使用 StartServiceCtrlDispatcher
注册的服务入口函数 ServiceMain
中,我们需要使用
RegisterServiceCtrlHandlerEx
注册服务的 HandlerProc
消息处理函数,然后使用注册后
得到的 SERVICE_STATUS_HANDLE
句柄,来注册U盘设备监控的通知函数
1 2 3 4 5
| #include <windows.h> #include <winioctl.h> #include <dbt.h> SERVICE_STATUS_HANDLE g_hServiceStatus = NULL; HDEVNOTIFY g_hDevNotify = NULL;
|
1.服务入口函数
注册监控时,我们只能使用 DBT_DEVTYP_DEVICEINTERFACE
或 DBT_DEVTYP_HANDLE
类型,
其中 DBT_DEVTYP_HANDLE
是针对某个设备的各种IO行为的监控,不符合我们的预期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| VOID WINAPI ServiceMain(DWORD argc, LPWSTR *argv) { g_hServiceStatus = RegisterServiceCtrlHandlerExW(L"MyService", &HandlerProc, NULL); if (!g_hServiceStatus) return; DEV_BROADCAST_DEVICEINTERFACE stDbdi = { 0 }; stDbdi.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); stDbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; stDbdi.dbcc_classguid = GUID_DEVINTERFACE_VOLUME; g_hDevNotify = RegisterDeviceNotificationW( g_hServiceStatus, &stDbdi, DEVICE_NOTIFY_SERVICE_HANDLE); if (!g_hDevNotify) return; ChangeServiceStatus(SERVICE_RUNNING); }
|
2.服务消息处理函数
在消息处理函数中,当Volume设备接入时,会触发 SERVICE_CONTROL_DEVICEEVENT
消息
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
| DWORD WINAPI HandlerProc(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) { switch (dwControl) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: UnregisterDeviceNotification(g_hDevNotify); ChangeServiceStatus(SERVICE_STOPPED); break;
case SERVICE_CONTROL_DEVICEEVENT: if (!lpEventData) break; switch (dwEventType) { case DBT_DEVICEARRIVAL: { PDEV_BROADCAST_HDR pDbch = (PDEV_BROADCAST_HDR)lpEventData; if (pDbch->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) break; GetVolumeDriveName(((PDEV_BROADCAST_DEVICEINTERFACE)pDbch)->dbcc_name); } break;
case DBT_DEVICEREMOVECOMPLETE: break; } break; } return NO_ERROR; }
|
3.根据Volume获取盘符
监控到U盘接入时,获取到的设备是 DBT_DEVTYP_DEVICEINTERFACE
类型的,而不是在窗口中
的 DBT_DEVTYP_VOLUME
类型,而 DEV_BROADCAST_DEVICEINTERFACE
的 dbcc_name
成员是
\\?\STORAGE#Volume#_??_USBSTOR#Disk&Ven_Kingston&Prod_DataTraveler_3.0 ...
的样式,通过使用 WinObj
工具查询,发现这个名称对应到了 \Device\HarddiskVolume???
的符号链接,我们知道盘符其实也是对应到这样的符号链接上,所以我们可以使用 ntdll.dll
导出的 NtOpenSymbolicLinkObject
和 NtQuerySymbolicLinkObject
函数来查询符号链接
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| WCHAR GetVolumeDriveName(PCWSTR pDevName) { if (!pDevName || !pDevName[0]) return 0; WCHAR wszBuffer[512] = { 0 }; wcscpy_s(wszBuffer, pDevName); if (wcsncmp(wszBuffer, L"\\\\?\\", 4)) return 0; wszBuffer[1] = L'?'; HMODULE hNtdll = LoadLibraryW(L"ntdll.dll"); if (!hNtdll) return 0; NTOPENSYMBOLICLINKOBJECT fnNtOpenSymbolicLinkObject = (NTOPENSYMBOLICLINKOBJECT)GetProcAddress(hNtdll, "NtOpenSymbolicLinkObject"); NTQUERYSYMBOLICLINKOBJECT fnNtQuerySymbolicLinkObject = (NTQUERYSYMBOLICLINKOBJECT)GetProcAddress(hNtdll, "NtQuerySymbolicLinkObject"); NTCLOSE fnNtClose = (NTCLOSE)GetProcAddress(hNtdll, "NtClose"); if (!fnNtOpenSymbolicLinkObject || !fnNtQuerySymbolicLinkObject || !fnNtClose) { FreeLibrary(hNtdll); return 0; } UNICODE_STRING strDevName = { 0 }; OBJECT_ATTRIBUTES objAttrib = { 0 }; strDevName.Buffer = wszBuffer; strDevName.MaximumLength = sizeof(wszBuffer); strDevName.Length = (USHORT)wcslen(wszBuffer) * sizeof(WCHAR); InitializeObjectAttributes(&objAttrib, &strDevName, OBJ_CASE_INSENSITIVE, NULL, NULL); HANDLE hSymLink = NULL; NTSTATUS ntStatus = fnNtOpenSymbolicLinkObject(&hSymLink, SYMBOLIC_LINK_QUERY, &objAttrib); if (ntStatus != STATUS_SUCCESS) { FreeLibrary(hNtdll); return 0; } ULONG dwLength = 0; strDevName.Length = 0; ZeroMemory(wszBuffer, sizeof(wszBuffer)); ntStatus = fnNtQuerySymbolicLinkObject(hSymLink, &strDevName, &dwLength); fnNtClose(hSymLink); FreeLibrary(hNtdll); if (ntStatus != STATUS_SUCCESS) return 0; WCHAR wszDrive[128] = { 0 }; WCHAR wszVolume[32] = { 0 }; DWORD dwNum = GetLogicalDriveStringsW(128, wszDrive); for (DWORD i = 0; i < dwNum; i += 4) { wszDrive[i + 2] = 0; if (GetDriveType(&wszDrive[i]) != DRIVE_REMOVABLE) continue; if (!QueryDosDeviceW(&wszDrive[i], wszVolume, 32)) continue; if (!_wcsicmp(wszVolume, wszBuffer)) return wszDrive[i]; } return 0; }
|