In this article I will describe how to detect device changes in your user-mode applications on the Windows operating system, IOW how to detect the situation when new devices are plugged or removed from the PC (i.e. plug-in USB stick, modem device or mobile phone, mount/unmount the new disk). Basic approach is receiving notification messages from the system.
Contents:
Hardware change detection
Windows sends events to top-level windows about arriving and removing a USB device interface. All we need to do is to add a handler to handle this event. First of all, we should register our windows to receive device notifications by calling RegisterDeviceNotification
function.
This function is defined as follows:
HDEVNOTIFY WINAPI RegisterDeviceNotification(
__in HANDLE hRecipient,
__in LPVOID NotificationFilter,
__in DWORD Flags
);
For the NotificationFilter
parameter we will use constant DBT_DEVTYP_DEVICEINTERFACE
:
GUID guidForModemDevices = {0x2c7089aa, 0x2e0e, 0x11d1,
{0xb1, 0x14, 0x00, 0xc0, 0x4f, 0xc2, 0xaa, 0xe4}};
DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
ZeroMemory( ¬ificationFilter, sizeof(notificationFilter) );
notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
notificationFilter.dbcc_classguid = guid;
Window creating
The hRecipient
parameter in described above RegisterDeviceNotification
function is a window handle or a service status handle. In our sample we will use window handle.
You can use existed handle of your top-level window in you UI application. If your application doesnโt have user interface you can create hidden window by registering instance of the WNDCLASSEX
class and calling RegisterClassEx
function by Windows API. Then create window and show it with parameter SW_HIDE
:
const wchar_t winClass[] = L"MyNotifyWindow";
const wchar_t winTitle[] = L"WindowTitle";
HINSTANCE hInstance = ::GetModuleHandle(NULL);
HWND hWnd = ::CreateWindow(winClass, winTitle, WS_ICONIC, 0, 0,
CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_HIDE);
Event handling
In this article we will handle only DBT_DEVICEARRIVAL
and DBT_DEVICEREMOVECOMPLETE
types of device changes. First, we should handle WM_DEVICECHANGE
event in WndProc
method by analyzing message parameter. Then, filter our two types of events by analyzing wParam
parameter:
LRESULT CALLBACK NotifyWindow::WndProc
(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DEVICECHANGE:
{
if ((wParam == DBT_DEVICEARRIVAL) || (wParam == DBT_DEVICEREMOVECOMPLETE))
โฆ
}
โฆ
}
โฆ
}
The parameter lParam
gives us information about the type and wParam
about the state (arrival or removed) of the device:
DEV_BROADCAST_HDR* header = reinterpret_cast<dev_broadcast_hdr*>(lParam);
if (header->dbch_devicetype == DBT_DEVTYP_VOLUME)
{
if (DBT_DEVICEARRIVAL == wParam)
{
// volume arrival
}
else
{
// volume removed
}
}
if (header->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{โฆ}
For mass-storage devices we should handle DBT_DEVTYP_VOLUME
device type because it means that a label has already been assigned to the volume and volume is ready to use.
Globally Unique Identifiers (GUIDs)
Plug-and-Play device can be associated with one or more different GUIDs. One device can have several interfaces. There are device class GUID and device interface GUIDs.
Some of GUIDs for device interface classes are listed below:
GUID_DEVINTERFACE_DISK = {53F56307-B6BF-11D0-94F200A0C91EFB8B}
GUID modemDevice = {2C7089AA-2E0E-11D1-B11400C04FC2AAE4};
GUID GarminGPSDevice = {2C9C45C2-8E7D-4C08-A12D816BBAE722C0};
For handling all interfaces you want, you should call RegisterDeviceNotification
for each GUID.
Windows Services
You can handle device notifications in your Windows Service. To do this, you should make changes in the code given above. When you register for notification, you should use DEVICE_NOTIFY_SERVICE_HANDLE
constant in RegisterDeviceNotification
function and handle SERVICE_CONTROL_DEVICEEVENT
service control message, which is similar to WM_DEVICECHANGE
event. Also your can use technic with hidden windows described above.
Retrieving of information about already connected devices
After implementing everything mentioned above, you will be notified about device changes. It will be possible only if your user-mode application had been started before the device was plugged in or removed. But for different reasons we should enumerate existing devices in the system after launching of our application. Windows has a set of APIs that give us a possibility to retrieve hardware device information. For example, we can get the device description or device friendly name. We will need to use such functions as SetupDiGetClassDevs
, SetupDiEnumDeviceInterfaces
, SetupDiGetDeviceInterfaceDetail
, SetupDiGetDeviceRegistryProperty
, etc. These methods are defined in setupapi.h. First, we should call SetupDiGetClassDevs
function to retrieve a handle of device info HDEVINFO
set. Then, we should use SetupDiEnumDeviceInterfaces
function to enumerate interfaces:
GUID guid = {โฆ} // some device interface GUID
HDEVINFO hDevInfo = ::SetupDiGetClassDevsW
(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
SP_DEVICE_INTERFACE_DATA devInterfaceData;
::ZeroMemory(&devInterfaceData, sizeof(SP_DEVICE_INTERFACE_DATA));
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for ( DWORD dwCount = 0; ::SetupDiEnumDeviceInterfaces
(hDevInfo, NULL, guid, dwCount, &devInterfaceData); ++dwCount )
{
โฆ
}
::SetupDiDestroyDeviceInfoList(hDevInfo);
After retrieving device interface data we can call SetupDiGetDeviceInterfaceDetail
function and retrieve such information as friendly name, device description, hardware id, device path.
Sample
In source code for this article, there is functionality to detect device changes for mass-storage devices, modem devices (for Motorola) and BlackBerry smartphone. To add more interfaces support, you should change the code (define new GUIDs or write filters to detect specific devices).
The source code contains static library DeviceDetectLibrary
and console application DeviceDetectConsole
. The main class is DeviceWatcher
with such methods as Start()
, Stop()
, Found()
, Lost()
. By subclassing this class you can use these methods to write handler for them.
Lets look how it works. First, we launch our DeviceDetectConsole
. Then, we plug-in USB Flash drive to our PC and open Device Manager. Here we can see JetFlash Transcend USB device:
Take a look at our application and see the detection process with the new appeared device:
The test application just shows status of the new devices (appeared or disappeared). This sample can show you devices that have been attached before you launched an application too.
Now letโs test the modem device. For test we take mobile phone Motorola V3C and BlackBerry smartPhone and connect them to PC:
Letโs take a look at our application:
For BlackBerry device:
Application successfully detected the new devices.
Conclusion
For different purposes, we need to receive system notifications about device changes. In this article I show some basic methods for doing this. In the source code of the sample you can find a lot of useful functions that you can use for receiving notifications, some low-level information and description of devices.