Un driver ou une application a souvent besoin d'être informé lorsqu'un périphérique apparaît, disparaît ou change d'état.
Windows fournit plusieurs couches de notifications PnP :
- messages de fenêtre en user mode ;
- notifications pour services ;
- callbacks en kernel mode ;
- notifications PnP personnalisées dans certains scénarios.
1. Vue générale
Code: Select all
PnP Manager
|
+--> IRP_MJ_PNP vers la device stack
|
+--> callbacks kernel enregistrés
|
+--> notifications aux services
|
+--> WM_DEVICECHANGE aux applications concernées
Une application possédant une fenêtre peut recevoir :
WM_DEVICECHANGE
Dans sa Window Procedure :
Code: Select all
case WM_DEVICECHANGE:
// wParam indique le type d'événement
// lParam peut pointer vers une structure DEV_BROADCAST_*
break;
- DBT_DEVICEARRIVAL : arrivée d'un périphérique/interface.
- DBT_DEVICEREMOVECOMPLETE : retrait terminé.
- DBT_DEVICEQUERYREMOVE : demande de retrait dans certains scénarios.
- DBT_DEVICEQUERYREMOVEFAILED : retrait finalement non autorisé/annulé.
Pour demander certaines notifications liées à une classe d'interface ou un handle :
RegisterDeviceNotificationW
Exemple pour une classe d'interface :
Code: Select all
DEV_BROADCAST_DEVICEINTERFACE_W filter = {0};
filter.dbcc_size = sizeof(filter);
filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
filter.dbcc_classguid = GUID_DEVINTERFACE_MYDEVICE;
HDEVNOTIFY notify = RegisterDeviceNotificationW(
hwnd,
&filter,
DEVICE_NOTIFY_WINDOW_HANDLE
);
UnregisterDeviceNotification
4. DEV_BROADCAST_DEVICEINTERFACE
Lorsque l'événement concerne une interface, lParam de WM_DEVICECHANGE peut pointer vers une structure de type :
Code: Select all
DEV_BROADCAST_DEVICEINTERFACE
- le type d'événement ;
- le GUID de classe d'interface ;
- le nom/chemin de l'interface concernée.
5. Alternative moderne sans fenêtre
Pour du code moderne ciblant Windows récents, le Configuration Manager expose aussi :
CM_Register_Notification
avec :
CM_Unregister_Notification
Ce modèle permet de recevoir des notifications PnP via callback sans exiger une fenêtre recevant WM_DEVICECHANGE.
Il utilise notamment des structures CM_NOTIFY_FILTER pour préciser ce qui doit être surveillé.
6. Notifications dans un service Windows
Un service peut recevoir des événements device via le Service Control Manager.
Dans les modèles de service classiques, il enregistre des notifications avec RegisterDeviceNotification en utilisant un handle de service et le flag approprié :
Code: Select all
DEVICE_NOTIFY_SERVICE_HANDLE
Code: Select all
SERVICE_CONTROL_DEVICEEVENT
7. Notifications kernel avec IoRegisterPlugPlayNotification
Un driver kernel peut enregistrer un callback :
IoRegisterPlugPlayNotification
Forme logique :
Code: Select all
NTSTATUS IoRegisterPlugPlayNotification(
IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
ULONG EventCategoryFlags,
PVOID EventCategoryData,
PDRIVER_OBJECT DriverObject,
PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
PVOID Context,
PVOID* NotificationEntry
);
8. Catégories de notifications kernel
Les catégories importantes sont :
- EventCategoryDeviceInterfaceChange : activation/désactivation d'interfaces de périphérique appartenant à une classe GUID.
- EventCategoryTargetDeviceChange : événements relatifs à un périphérique cible précis identifié par un FILE_OBJECT.
- EventCategoryHardwareProfileChange : changements de profil matériel.
9. INCLUDE_EXISTING_INTERFACES
Avec EventCategoryDeviceInterfaceChange, le flag :
Code: Select all
PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES
C'est important pour éviter une race du type :
Code: Select all
Périphérique déjà présent
|
Driver s'enregistre seulement pour les événements futurs
|
Aucun événement d'arrivée ne se produit
|
Driver ne découvre jamais l'interface existante
Prototype conceptuel :
Code: Select all
NTSTATUS PnpNotifyCallback(
PVOID NotificationStructure,
PVOID Context
)
{
// analyser le header et le GUID d'événement
return STATUS_SUCCESS;
}
PLUGPLAY_NOTIFICATION_HEADER
Il contient notamment le GUID décrivant le type précis d'événement.
11. GUID d'événements courants
Pour les interfaces de périphériques, on rencontre notamment :
- GUID_DEVICE_INTERFACE_ARRIVAL : une interface a été activée.
- GUID_DEVICE_INTERFACE_REMOVAL : une interface a été désactivée/retirée.
Le callback doit toujours identifier le type exact de structure à partir de la catégorie et du GUID avant de caster NotificationStructure.
12. IoUnregisterPlugPlayNotification
L'inscription retourne un handle opaque via NotificationEntry.
Avant que le driver puisse être déchargé ou que le contexte utilisé par le callback soit détruit :
IoUnregisterPlugPlayNotification
ou, selon le besoin de synchronisation et les APIs disponibles dans le modèle ciblé, la variante Ex documentée pour attendre la fin des callbacks en cours.
Règle : ne jamais libérer le contexte tant qu'un callback enregistré peut encore être appelé.
13. Target Device Change
Une target-device notification concerne un périphérique précis plutôt qu'une classe entière.
Elle s'appuie généralement sur un FILE_OBJECT obtenu après ouverture/référence du device cible.
Elle est utile lorsqu'un composant veut être informé que CE périphérique particulier va être retiré ou a changé d'état.
14. Custom PnP notifications
Un driver peut signaler une notification personnalisée liée à un target device avec :
- IoReportTargetDeviceChange
- IoReportTargetDeviceChangeAsynchronous
Elles ne doivent pas être utilisées pour recréer artificiellement les notifications standard déjà générées par le PnP Manager.
Une structure de notification personnalisée inclut un GUID d'événement choisi par le fournisseur ainsi qu'un payload éventuel.
15. Ordre d'un surprise removal
Lorsqu'un device disparaît brutalement, la stack PnP est informée avant les observateurs externes.
Conceptuellement :
Code: Select all
Hardware disparaît
|
PnP Manager détecte la disparition
|
IRP_MN_SURPRISE_REMOVAL vers la stack
|
Drivers arrêtent les accès hardware
|
Notifications kernel/user mode
|
IRP_MN_REMOVE_DEVICE plus tard
16. Notifications vs device interfaces
Le lien est direct :
Code: Select all
Driver A
IoRegisterDeviceInterface
IoSetDeviceInterfaceState(TRUE)
|
v
PnP publie l'interface
|
+--> IoRegisterPlugPlayNotification callbacks kernel
+--> CM_Register_Notification callbacks user mode
+--> WM_DEVICECHANGE pour applications inscrites
17. API essentielles : user mode
- RegisterDeviceNotificationW : enregistre une fenêtre ou un service pour certaines notifications device.
- UnregisterDeviceNotification : supprime l'inscription.
- WM_DEVICECHANGE : message reçu par une fenêtre.
- CM_Register_Notification : modèle de callback moderne du Configuration Manager.
- CM_Unregister_Notification : supprime cette inscription.
- IoRegisterPlugPlayNotification : enregistre un callback PnP kernel.
- IoUnregisterPlugPlayNotification : désenregistre le callback.
- IoReportTargetDeviceChange : envoie une notification target-device personnalisée synchrone.
- IoReportTargetDeviceChangeAsynchronous : variante asynchrone.
- PLUGPLAY_NOTIFICATION_HEADER : header commun permettant d'identifier le type d'événement.
- Confondre une notification PnP avec une IRP PnP.
- Oublier de désenregistrer un callback avant l'unload du driver.
- Libérer le Context alors qu'un callback peut encore arriver.
- Casteur NotificationStructure sans vérifier le type/GUID de l'événement.
- En user mode, supposer que WM_DEVICECHANGE donne toujours assez d'informations sans examiner la structure DEV_BROADCAST appropriée.
- Ne surveiller que les futures interfaces et oublier celles déjà présentes.
- Réutiliser les limitations Windows 98/Me décrites dans les vieux ouvrages comme si elles s'appliquaient encore à Windows 10/11.
Trois niveaux principaux :
Code: Select all
USER MODE GUI
WM_DEVICECHANGE
RegisterDeviceNotification
USER MODE MODERNE / SERVICE
CM_Register_Notification
ou notifications SCM selon le modèle
KERNEL MODE
IoRegisterPlugPlayNotification
