(WDF) 使用设备接口
设备接口是指向应用程序可用于访问设备的即插即用 (PnP) 设备的符号链接。 用户模式应用程序可以将接口的符号链接名称传递给 API 元素,例如 Microsoft Win32 CreateFile 函数。 若要获取设备接口的符号链接名称,用户模式应用程序可以调用 配置管理器函数 或 SetupApi 函数。 有关详细信息,请参阅 枚举已安装的设备接口。
每个设备接口都属于一个 设备接口类。 例如,CD-ROM 设备的驱动程序堆栈可能会提供属于 GUID_DEVINTERFACE_CDROM 类的接口。 其中一个 CD-ROM 设备的驱动程序会注册 GUID_DEVINTERFACE_CDROM 类的实例,以通知系统和应用程序 CD-ROM 设备可用。 有关设备接口类的详细信息,请参阅 设备接口类概述。
注册设备接口
若要注册设备接口类的实例,基于框架的驱动程序可以在设备启动之前或之后调用 WdfDeviceCreateDeviceInterface 。 如果驱动程序支持接口的多个实例,则可以为每个实例分配唯一的引用字符串。
驱动程序注册设备接口后,驱动程序可以调用 WdfDeviceRetrieveDeviceInterfaceString 来获取系统已分配给设备接口的符号链接名称。
有关驱动程序注册设备接口的其他方法的信息,请参阅 注册设备接口类。
启用和禁用设备接口
在设备启动之前创建的接口 (例如,当设备通过 PnP 枚举并启动时,框架会自动启用从 EvtDriverDeviceAdd、 EvtChildListCreateDevice 或 EvtDevicePrepareHardware) 创建的接口。 若要防止在 PnP 启动期间自动启用接口,请从同一回调函数调用 WdfDeviceSetDeviceInterfaceStateEx , (在 PnP 启动之前将该接口的 EnableInterface 参数设置为 FALSE) 。
在设备已启动后创建的接口不会自动启用。 驱动程序必须调用 WdfDeviceSetDeviceInterfaceState 或 WdfDeviceSetDeviceInterfaceStateEx 才能启用此类接口。
当设备进行 PnP 删除时,将自动禁用所有接口。 请注意,任何设备电源状态更改或 PnP 资源重新平衡都不会更改接口的状态。
如有必要,驱动程序可以禁用和重新启用设备接口。 例如,如果驱动程序确定其设备已停止响应,则驱动程序可以调用 WdfDeviceSetDeviceInterfaceState 或 WdfDeviceSetDeviceInterfaceStateEx 来禁用设备的接口并禁止应用程序获取接口的新句柄。 (接口的现有句柄不受影响。) 如果设备稍后可用,驱动程序可以再次调用 WdfDeviceSetDeviceInterfaceState 或 WdfDeviceSetDeviceInterfaceStateEx 以重新启用接口。
接收访问设备接口的请求
当应用程序或内核模式组件请求访问驱动程序的设备接口时,框架会调用驱动程序的 EvtDeviceFileCreate 回调函数。 驱动程序可以调用 WdfFileObjectGetFileName ,以获取应用程序或内核模式组件正在访问的设备或文件的名称。 如果驱动程序在注册设备接口时指定了引用字符串,则操作系统将在 WdfFileObjectGetFileName 返回的文件或设备名称中包含引用字符串。
访问其他驱动程序的设备接口
本部分介绍Kernel-Mode驱动程序框架 (KMDF) 驱动程序或User-Mode驱动程序框架如何 (UMDF) 版本 2 驱动程序注册以通知其他驱动程序提供的设备接口的到达或删除,然后创建 远程 I/O 目标 以与设备接口表示的设备通信。
有关如何在 UMDF 版本 1 驱动程序中执行此操作的信息,请参阅 在 UMDF 驱动程序中使用设备接口。
为了注册设备接口事件的通知,KMDF 驱动程序调用 IoRegisterPlugPlayNotification,而 UMDF 2 驱动程序调用 CM_Register_Notification。 在这两种情况下,驱动程序从其 EvtDriverDeviceAdd 回调函数调用相应的例程。
下面的代码示例演示本地 UMDF 2 驱动程序如何注册通知,然后打开远程 I/O 目标。
远程驱动程序通过从 EvtDriverDeviceAdd 调用 WdfDeviceCreateDeviceInterface 来注册设备接口。
UNICODE_STRING ref; RtlInitUnicodeString(&ref, MY_HID_FILTER_REFERENCE_STRING); status = WdfDeviceCreateDeviceInterface( hDevice, (LPGUID) &GUID_DEVINTERFACE_MY_HIDFILTER_DRIVER, &ref // ReferenceString ); if (!NT_SUCCESS (status)) { MyKdPrint( ("WdfDeviceCreateDeviceInterface failed 0x%x\n", status)); return status; }
本地驱动程序从 EvtDriverDeviceAdd 调用 CM_Register_Notification,以便在设备接口可用时注册通知。 提供指向当设备接口可用时框架调用的通知回调例程的指针。
DWORD cmRet; CM_NOTIFY_FILTER cmFilter; ZeroMemory(&cmFilter, sizeof(cmFilter)); cmFilter.cbSize = sizeof(cmFilter); cmFilter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE; cmFilter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_MY_HIDFILTER_DRIVER; cmRet = CM_Register_Notification( &cmFilter, // PCM_NOTIFY_FILTER pFilter, (PVOID) hDevice, // PVOID pContext, MyCmInterfaceNotification, // PCM_NOTIFY_CALLBACK pCallback, &fdoData->CmNotificationHandle // PHCMNOTIFICATION pNotifyContext ); if (cmRet != CR_SUCCESS) { MyKdPrint( ("CM_Register_Notification failed, error %d\n", cmRet)); status = STATUS_UNSUCCESSFUL; return status; }
每次指定的设备接口到达或删除时,系统都会调用本地驱动程序的通知回调例程。 回调例程可以检查 EventData 参数以确定到达的设备接口。 然后,它可能会将工作项排队以打开设备接口。
DWORD MyCmInterfaceNotification( _In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context, _In_ CM_NOTIFY_ACTION Action, _In_reads_bytes_(EventDataSize) PCM_NOTIFY_EVENT_DATA EventData, _In_ DWORD EventDataSize ) { PFDO_DATA fdoData; UNICODE_STRING name; WDFDEVICE device; NTSTATUS status; WDFWORKITEM workitem; UNREFERENCED_PARAMETER(hNotify); UNREFERENCED_PARAMETER(EventDataSize); device = (WDFDEVICE) Context; fdoData = ToasterFdoGetData(device); switch(Action) { case CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL: MyKdPrint( ("MyCmInterfaceNotification: Arrival of %S\n", EventData->u.DeviceInterface.SymbolicLink)); // // Enqueue a work item to open target // break; case CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL: MyKdPrint( ("MyCmInterfaceNotification: removal of %S\n", EventData->u.DeviceInterface.SymbolicLink)); break; default: MyKdPrint( ("MyCmInterfaceNotification: Arrival unknown action\n")); break; } return 0; }
从工作项回调函数中,本地驱动程序调用 WdfIoTargetCreate 来创建远程目标,并调用 WdfIoTargetOpen 打开远程 I/O 目标。
调用 WdfIoTargetOpen 时,驱动程序可以选择注册 EvtIoTargetQueryRemove 回调函数来接收删除通知以及拒绝删除的机会。 如果驱动程序不提供 EvtIoTargetQueryRemove,框架在删除设备时关闭 I/O 目标。
在极少数情况下,UMDF 2 驱动程序可以再次调用 CM_Register_Notification ,以注册设备删除通知。 例如,如果驱动程序调用 CreateFile 来获取设备接口的 HANDLE,它应注册设备删除通知,以便可以正确响应查询删除尝试。 在大多数情况下,UMDF 2 驱动程序只 调用CM_Register_Notification 一次,并且依赖于 WDF 支持来删除设备。
VOID EvtWorkItem( _In_ WDFWORKITEM WorkItem ) { // // create and open remote target // return; }