Windows PnP Notifications: Track Device Interfaces Without Losing the Snapshot
Register for Windows device-interface changes, reconcile current inventory, and keep callbacks, handles, and unregister operations safe.
Windows Plug and Play notifications are useful when an application must react to a device interface becoming enabled or disabled. They are not a complete device inventory, a guarantee that hardware is ready for I/O, or a substitute for opening the interface with the correct access. Treat notifications as change signals and reconcile them with a fresh snapshot.
For user-mode applications targeting Windows 8 and later, CM_Register_Notification registers a callback using Configuration Manager. Microsoft directs applications that must support Windows 7 or earlier to RegisterDeviceNotification; kernel-mode drivers use a different API. Pick the notification model for the minimum OS and caller type instead of copying a driver example into a desktop application.
Subscribe, then enumerate what already exists
An interface-class GUID identifies the device interface family the application cares about. Choose the GUID published for the actual API or device family; do not use a broad device-instance filter when the product only needs one interface type.
CM_NOTIFY_FILTER filter{};
filter.cbSize = sizeof(filter);
filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
filter.u.DeviceInterface.ClassGuid = interfaceClassGuid;
HCMNOTIFICATION notification = nullptr;
CONFIGRET status = CM_Register_Notification(
&filter, callbackContext, DeviceInterfaceCallback, ¬ification);
if (status != CR_SUCCESS) {
// Preserve the CONFIGRET for diagnostics; do not use GetLastError here.
}
This is a registration fragment, not a complete callback implementation: initialize the context, its lock, and worker state before registration, and keep the callback short. Microsoft notes that PnP callbacks should avoid blocking work; queue I/O or other slow processing to a worker. A callback can fire before CM_Register_Notification returns, so do not make callback setup depend on code that runs only after that return. Keep the API’s output handle in a caller-local variable and use it only after successful return. If an early callback queues a worker that needs the handle, copy the callback’s hNotify argument into a pre-initialized shared context field under its lock before queuing the work; have the worker read it under the same lock. Keep that context alive until unregistration and queued work have completed.
Registration does not report interfaces that were already present. Register first, then call CM_Get_Device_Interface_List (or the documented SetupAPI equivalent) to enumerate the current set. An interface may arrive between those operations and appear both in the callback stream and the enumeration result. Merge the two sources into an idempotent snapshot keyed by the current interface symbolic-link name, and tolerate duplicates. When the size/list calls race with another arrival, retry if the list call returns CR_BUFFER_SMALL.
The interface symbolic link is an access path for the current instance, not a durable product identity. If the application needs to remember a physical device across reconnects, collect and validate the appropriate device identity separately. An arrival notification only says that the matching interface was enabled; it does not grant permissions or guarantee that CreateFile will succeed.
Treat interface changes and open-handle removal as separate events
CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE reports interface arrival and removal. If the application opens a device and must coordinate query-remove, Microsoft documents a second registration using CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE for that specific handle. On CM_NOTIFY_ACTION_DEVICEQUERYREMOVE, close the handle so an orderly removal can proceed. Returning ERROR_CANCELLED vetoes that request; do so only when the product has a clear, documented reason to block removal.
If a query-remove fails after the application closed its handle, Microsoft says the interface remains valid but the application must open a new handle before resuming I/O. Keep that recovery separate from interface enumeration: a failed removal request does not mean a newly arrived device. Device access can still fail because of ACLs, driver readiness, access flags, or a race with removal, so handle every open and I/O result.
Unregister without deadlocking or freeing callback state early
Call CM_Unregister_Notification when the listener is no longer needed, then release its context only after unregistration has completed. Microsoft explicitly warns not to call it from the notification callback: unregister waits for pending callbacks, so calling it there can deadlock. If shutdown begins inside a callback, schedule unregistration on a separate worker and keep the context alive until that work finishes. Also stop or drain any application work items that retained copied device identifiers before freeing shared state.
Exercise the lifecycle with a harmless test device or a VM that supports device-interface changes: start with a device already present, attach and remove it, trigger a failed open, request orderly removal while a handle is open, and shut the listener down during a burst of notifications. Verify that duplicates do not produce duplicate resources, that callback work stays bounded, and that the app can enumerate again after reconnect. Do not test by ejecting a production disk or disabling an essential driver.
The durable design is a short callback, a current-state inventory, and explicit ownership for every notification and device handle. PnP reports state transitions; the application still owns policy, access checks, retries, and cleanup.
Related:
- Windows PnP Runtime Diagnostics: Devnodes, Problem Codes, and Device State
- WinUSB Overlapped Pipe I/O: Completion, Cancellation, and Teardown
Sources: