在 PnP 环境中,驱动程序和应用程序需要对计算机上的设备配置更改做出反应。 例如,应用程序需要知道何时将感兴趣的设备添加到计算机,驱动程序需要知道何时在特定设备上发生更改。
PnP 管理器提供一种机制,以便在发生某些 PnP 事件时通知驱动程序和应用程序,当特定设备或一般系统上发生某些事件时,驱动程序和应用程序会收到通知。这个在应用层中已经被广泛的使用了,例如MFC的WM_CHANCEDEVICE;另外也有注册通知的例子,用户模式应用程序调用 RegisterDeviceNotification 函数来注册自身,以在将处理器或内存模块动态添加到硬件分区时收到通知。 应用程序调用 RegisterDeviceNotification 函数两次,一次用于注册处理器事件通知,另一次调用注册内存事件通知。 应用程序在注册这些事件的通知时指定以下 GUID 之一(这些 GUID 在头文件 Poclass.h中) :
GUID_DEVICE_PROCESSOR
注册应用程序,以在将处理器动态添加到硬件分区时收到通知。
GUID_DEVICE_MEMORY
注册应用程序,以在内存动态添加到硬件分区时收到通知。
如果应用程序只需要收到有关处理器的通知,则它不必注册内存事件通知。 同样,如果应用程序只需要收到有关内存的通知,则它不必注册处理器事件的通知。
当应用程序不再需要接收处理器或内存事件的通知时,它可以通过调用 UnregisterDeviceNotification 函数取消注册窗口,使其从接收这些事件的WM_DEVICECHANGE消息。
驱动程序可以注册以下类别的事件通知:
1. EventCategoryDeviceInterfaceChange:当驱动程序在设备接口上注册此类别的事件时,PnP 管理器会通知驱动程序以下事件:
- GUID_DEVICE_INTERFACE_ARRIVAL;
- 指示指定类的设备接口已启用。 例如,用户向计算机添加了一个新磁盘,卷管理器 (类“volume”) 的设备接口启用了新卷;
- GUID_DEVICE_INTERFACE_REMOVAL
- 指示已禁用指定类的设备接口;
2. EventCategoryTargetDeviceChange: 当驱动程序在设备上注册此类别的事件时,当设备上发生以下事件时,PnP 管理器会通知驱动程序:
GUID_TARGET_DEVICE_QUERY_REMOVE
指示 PnP 管理器即将删除设备的驱动程序。 多个操作可能会导致此事件,包括:用户已请求从计算机中删除指定的设备,或者用户已对设备发出更新驱动程序请求。 此通知请求设备的驱动程序批准或否决即将执行的删除操作。
- GUID_TARGET_DEVICE_REMOVE_COMPLETE;
- 指示指定的设备已从计算机中删除,或者用户正在更改设备的驱动程序;
- GUID_TARGET_DEVICE_REMOVE_CANCELLED;
- 指示指定设备上即将执行的删除操作已取消;
- GUID_XXX (自定义事件);
- 指示在指定的设备上发生了自定义事件;
驱动程序编写器可以为设备定义自定义事件。 当驱动程序 (或其他相关组件) 通知 PnP 管理器自定义事件已发生时,PnP 管理器会通知在设备上注册目标设备更改通知的任何组件。
与注册设备接口更改不同,注册设备接口更改可被视为接口中的“被动”关注,注册目标设备更改则表示对设备的“主动”关注。
3. EventCategoryHardwareProfileChange:此类别包括以下事件:
- GUID_HWPROFILE_QUERY_CHANGE:指示用户已请求更改计算机的硬件配置文件。 PnP 管理器使用此通知来询问已注册的组件是否可以在不中断系统操作的情况下更改硬件配置文件。 已注册的组件通常会成功执行这些查询请求;
- GUID_HWPROFILE_CHANGE_COMPLETE:指示计算机的硬件配置文件已更改。 如果驱动程序维护特定于配置文件的设置,此类驱动程序应在硬件配置文件更改后刷新这些设置;
- GUID_HWPROFILE_CHANGE_CANCELLED:指示即将发生的硬件配置文件更改已取消。;
对于内核模式组件,PnP 通知的工作方式如下:
- 驱动程序通过调用 IoRegisterPlugPlayNotification 注册事件类别的通知;在驱动程序显式删除注册之前,PnP 通知回调例程保持注册状态。
- 发生已注册类别中的事件时,PnP 管理器调用驱动程序的回调例程。
- 驱动程序通过调用 IoUnregisterPlugPlayNotification 删除回调注册。
驱动程序不得在处理关闭期间生成同步事件或等待异步事件发生。
编写原则
PnP 管理器在 IRQL = PASSIVE_LEVEL 调用通知回调例程,为了确保 PnP 子系统的顺利运行,PnP 通知回调例程必须遵循以下准则:
1. 通知回调例程不得阻止;
2. 通知回调例程不得调用或导致调用生成 PnP 事件的同步例程或任何阻止等待设备安装或删除的例程,在通知回调期间调用此类例程可能会导致系统死锁。例如,驱动程序不得在通知回调例程中调用 IoReportTargetDeviceChange 。 请改为调用 IoReportTargetDeviceChangeAsynchronous ;
3. 通知回调例程应返回未显式失败的任何事件的成功,当驱动程序注册事件类别上的通知时,PnP 管理器会将该类别中的所有事件(当前和将来)通知驱动程序。 如果驱动程序返回它不处理的事件的错误状态,则驱动程序可能会错误地让新查询事件失败。例如,当驱动程序未通过查询通知以否决所建议的事件时,驱动程序会正确返回错误状态;
4. 通知回调例程应为分页代码;
注册设备接口更改通知
驱动程序通过调用 IoRegisterPlugPlayNotification 注册设备接口到达和删除事件的通知。
以下信息适用于为设备接口更改通知调用此例程:
1. 指定 EventCategoryDeviceInterfaceChange 的 EventCategory;
2. EventCategoryData 必须指向设备接口类的 GUID。接口类的 GUID 通常在头文件中定义,其中包含接口的结构、常量等;
3. 指定 PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES的 EventCategoryFlags 。此标志指示 PnP 管理器为指定类的未来设备接口到达和离开注册 CallbackRoutine ,并为已处于活动状态的任何相关设备接口立即调用 CallbackRoutine 。驱动程序可以调用 IoGetDeviceInterfaces 来获取特定类的现有接口的列表,然后在没有此标志的情况下注册其回调例程,但使用该标志会更容易,并避免潜在的计时问题;
4. 指定驱动程序定义的 上下文(如果适用),PnP 管理器将传递给回调例程;
打开设备句柄以响应设备接口到达通知的驱动程序应在设备上注册 EventCategoryTargetDeviceChange 事件;驱动程序通过使用 IoRegisterPlugPlayNotification 返回的 NotificationEntry 调用 IoUnregisterPlugPlayNotification 来取消通知注册。
处理设备接口更改事件
当驱动程序或用户模式组件启用或禁用设备接口实例时,PnP 管理器将调用在设备接口类上为 EventCategoryDeviceInterfaceChange 事件注册的所有通知回调例程。 为了指示通知的原因,PnP 管理器将回调例程的 NotificationStructure 参数的 Event 成员设置为GUID_DEVICE_INTERFACE_ARRIVAL或GUID_DEVICE_INTERFACE_REMOVAL。
处理GUID_DEVICE_INTERFACE_ARRIVAL事件时,通知回调例程应该执行驱动程序定义的任务来处理新接口,通常,通知回调例程直接在回调的上下文中打开设备。 但是,如果打开设备可能导致后续 PnP 事件发生 (例如,子设备的枚举) ,则回调例程应改为将辅助角色例程排队以打开设备;否则,可能会出现死锁。
回调例程可能会启用自己的接口,以响应新接口的可用性。
处理GUID_DEVICE_INTERFACE_REMOVAL事件时,通知回调例程应该撤消启用接口时执行的任何操作。 关闭为响应接口到达事件而打开的任何文件句柄。
除了 EventCategoryDeviceInterfaceChange 外,驱动程序还必须注册 EventCategoryTargetDeviceChange,并从 GUID_TARGET_DEVICE_QUERY_REMOVE 事件回调关闭文件句柄,使文件句柄保持打开状态将否决删除过程,并导致有序删除被取消。
注册目标设备更改通知
驱动程序通过调用 IoRegisterPlugPlayNotification 注册 PnP 目标设备更改事件的通知。
以下信息适用于为目标设备更改通知调用此例程:
1. 指定 EventCategoryTargetDeviceChange 的 EventCategory。
2. EventCategoryData 必须指向请求通知的设备的文件对象。如果驱动程序的回调例程需要访问文件对象,则驱动程序应在调用 IoRegisterPlugPlayNotification 之前对文件对象执行引用。
如果驱动程序的回调例程不需要访问文件对象,则驱动程序不需要引用对象。关闭文件对象后,驱动程序将继续接收设备的通知,直到驱动程序删除其通知注册。 例如,此设计允许驱动程序接收GUID_TARGET_DEVICE_REMOVE_CANCELLED事件的通知。
3. 指定 PnP 管理器将传递给回调例程的驱动程序定义的 上下文 。驱动程序可以使用 Context 参数来维护有关文件对象的当前状态的信息,例如,是否已关闭/删除文件对象;驱动程序还可以使用 Context 来存储最初用于打开设备的路径。 驱动程序可以使用此路径在取消删除操作后重新打开设备;
驱动程序使用 IoRegisterPlugPlayNotification 返回的 NotificationEntry 调用 IoUnregisterPlugPlayNotification 来删除通知注册。;如果驱动程序在注册通知时对文件对象进行了引用,并且该引用仍然未完成,则驱动程序必须在删除注册后释放该引用。
处理 GUID_TARGET_DEVICE_QUERY_REMOVE 事件
在 PnP 管理器将 IRP_MN_QUERY_REMOVE_DEVICE IRP 发送到设备的驱动程序之前,它会调用在设备上为 EventCategoryTargetDeviceChange 注册的任何通知回调例程。 PnP 管理器指定 NotificationStructure来处理GUID_TARGET_DEVICE_QUERY_REMOVE事件 。
为了响应此类通知,回调例程确定是否可以在不中断系统的情况下删除设备,如果不应删除设备,则回调例程将返回STATUS_UNSUCCESSFUL。 为了响应此状态,PnP 管理器会中止查询删除处理,并且不会删除设备。
如果可以删除设备,则回调例程应执行任何适当的操作来准备删除设备,例如关闭设备上打开的任何句柄。 如果句柄在设备上保持打开状态,则 PnP 管理器无法删除设备,并且 PnP 管理器会中止查询删除处理。
成功处理GUID_TARGET_DEVICE_QUERY_REMOVE事件时,通知回调例程应:
- 关闭设备的任何打开句柄;
- 如果驱动程序对文件对象具有未完成的引用,请取消引用文件对象;
- 为将来的 EventCategoryTargetDeviceChange 通知保持注册状态。 这一点很重要,因为可能会取消即将发生的删除操作;
关闭设备的句柄不会取消驱动程序注册 PnP 目标设备更改通知。 PnP 管理器仍然可以调用驱动程序的通知回调例程,但在此类调用中, NotificationStructure 中的文件对象无效。
处理 GUID_TARGET_DEVICE_REMOVE_COMPLETE 事件
在 PnP 管理器将 IRP_MN_REMOVE_DEVICE IRP 发送到设备的驱动程序之前,PnP 管理器会调用在设备上为 EventCategoryTargetDeviceChange 注册的任何内核模式通知回调例程。 PnP 管理器指定 NotificationStructure。GUID_TARGET_DEVICE_REMOVE_COMPLETE事件 。
处理GUID_TARGET_DEVICE_REMOVE_COMPLETE事件时,通知回调例程应:
- 删除设备上的通知注册:设备已删除,因此驱动程序调用 IoUnregisterPlugPlayNotification 以删除通知注册。设备可能仍实际存在于计算机上,但已删除所有设备对象,并且设备不可用;
- 如果驱动程序未收到以前的查询删除通知,请执行意外删除处理。如果设备意外删除,PnP 管理器将向注册的驱动程序发送删除完成通知,而无需事先发出查询删除通知。 在这种情况下,驱动程序必须执行任何必要的清理,例如关闭设备的任何句柄并删除对文件对象的任何未完成的引用;
处理 GUID_TARGET_DEVICE_REMOVE_CANCELLED 事件
如果 IRP_MN_QUERY_REMOVE_DEVICE 请求失败,PnP 管理器会将 IRP_MN_CANCEL_REMOVE_DEVICE IRP 发送到设备的驱动程序。 取消-删除 IRP 成功完成后,PnP 管理器将调用设备上为 EventCategoryTargetDeviceChange 注册的任何通知回调例程。 PnP 管理器指定 NotificationStructure。GUID_TARGET_DEVICE_REMOVE_CANCELLED事件 。
处理GUID_TARGET_DEVICE_REMOVE_CANCELLED事件时,通知回调例程应重新注册目标设备通知。
由于驱动程序关闭了以前的注册句柄以响应查询删除通知,因此驱动程序必须打开一个新句柄。 驱动程序必须:
- 使用 IoUnregisterPlugPlayNotification 删除旧注册;
- 打开设备的新句柄;
- 使用 IoRegisterPlugPlayNotification 重新注册新句柄上的通知;
注册硬件配置文件更改通知
驱动程序通过调用 IoRegisterPlugPlayNotification 注册硬件配置文件更改的通知。
以下信息适用于调用此例程以获取硬件配置文件更改通知:
- 指定 EventCategoryHardwareProfileChange 的 EventCategory;
- EventCategoryData 必须为 NULL;
- 指定驱动程序定义的 上下文(如果适用),PnP 管理器将传递给回调例程;
驱动程序使用 IoRegisterPlugPlayNotification 返回的 NotificationEntry 调用 IoUnregisterPlugPlayNotification 来删除通知注册。
处理硬件配置文件更改事件
在硬件配置文件更改期间的特定时间,PnP 管理器调用为 EventCategoryHardwareProfileChange 注册的通知回调例程:
- 在计算机的硬件配置文件发生更改之前,PnP 管理器会调用已注册的通知回调例程并指定 NotificationStructure。GUID_HWPROFILE_QUERY_CHANGE事件 ;
- 计算机的硬件配置文件更改完成后,PnP 管理器会调用已注册的通知回调例程并指定 NotificationStructure。GUID_HWPROFILE_CHANGE_COMPLETE事件;
- 如果取消计算机的硬件配置文件更改,PnP 管理器将调用已注册的通知回调例程并指定 NotificationStructure。GUID_HWPROFILE_CHANGE_CANCELLED事件 ;
对于GUID_HWPROFILE_QUERY_CHANGE事件,PnP 管理器调用用户模式回调例程,然后调用内核模式回调例程。 为了响应GUID_HWPROFILE_QUERY_CHANGE事件,驱动程序的通知回调例程通常只返回STATUS_SUCCESS。
对于GUID_HWPROFILE_CHANGE_COMPLETE事件,PnP 管理器调用内核模式回调例程,然后调用用户模式回调例程。 为了响应此类事件,驱动程序的回调例程可能会刷新其特定于硬件配置文件的设置。
对于GUID_HWPROFILE_CHANGE_CANCELLED事件,PnP 管理器调用内核模式回调例程,然后调用用户模式例程。 为了响应此类事件,驱动程序的回调例程通常只返回STATUS_SUCCESS。 如果驱动程序执行任何操作以响应GUID_HWPROFILE_QUERY_CHANGE事件,驱动程序将撤消这些操作以响应取消事件。
使用 PnP 自定义通知
驱动程序可以使用目标设备更改通知机制在设备上收到自定义事件的通知。
定义自定义事件的程序员必须执行以下操作:
- 为自定义事件定义新的 GUID:使用 Uuidgen 或 Guidgen (生成 GUID,这些 GUID 包含在Microsoft Windows SDK) 中。 在相应的头文件和文档中发布 GUID;
- 编写代码以触发自定义事件:在内核模式下,驱动程序使用自定义 GUID 和指向设备的 PDO 的指针调用 IoReportTargetDeviceChange 。 自定义事件只能从内核模式触发;
驱动程序编写器通过如下过程使用自定义通知:
1. 驱动程序 (或应用程序) 注册自定义事件的通知:在内核模式下,驱动程序调用 IoRegisterPlugPlayNotification 并在设备上注册 EventCategoryTargetDeviceChange ;在用户模式下,应用程序使用 RegisterDeviceNotification 进行注册;
2. 内核模式组件触发自定义事件;
3. PnP 管理器调用在设备上注册的通知例程,PnP 管理器调用已注册的用户模式回调例程,然后调用内核模式回调例程;
4. 用户模式通知完成后,内核模式驱动程序通知回调例程响应自定义事件;
注意: 自定义通知回调例程不得从回调例程线程中打开设备的句柄。