(WDF) 使用设备接口

设备接口是指向应用程序可用于访问设备的即插即用 (PnP) 设备的符号链接。 用户模式应用程序可以将接口的符号链接名称传递给 API 元素,例如 Microsoft Win32 CreateFile 函数。 若要获取设备接口的符号链接名称,用户模式应用程序可以调用 配置管理器函数SetupApi 函数。 有关详细信息,请参阅 枚举已安装的设备接口

每个设备接口都属于一个 设备接口类。 例如,CD-ROM 设备的驱动程序堆栈可能会提供属于 GUID_DEVINTERFACE_CDROM 类的接口。 其中一个 CD-ROM 设备的驱动程序会注册 GUID_DEVINTERFACE_CDROM 类的实例,以通知系统和应用程序 CD-ROM 设备可用。 有关设备接口类的详细信息,请参阅 设备接口类概述

注册设备接口

若要注册设备接口类的实例,基于框架的驱动程序可以在设备启动之前或之后调用 WdfDeviceCreateDeviceInterface 。 如果驱动程序支持接口的多个实例,则可以为每个实例分配唯一的引用字符串。

驱动程序注册设备接口后,驱动程序可以调用 WdfDeviceRetrieveDeviceInterfaceString 来获取系统已分配给设备接口的符号链接名称。

有关驱动程序注册设备接口的其他方法的信息,请参阅 注册设备接口类

启用和禁用设备接口

在设备启动之前创建的接口 (例如,当设备通过 PnP 枚举并启动时,框架会自动启用从 EvtDriverDeviceAddEvtChildListCreateDeviceEvtDevicePrepareHardware) 创建的接口。 若要防止在 PnP 启动期间自动启用接口,请从同一回调函数调用 WdfDeviceSetDeviceInterfaceStateEx , (在 PnP 启动之前将该接口的 EnableInterface 参数设置为 FALSE) 。

在设备已启动后创建的接口不会自动启用。 驱动程序必须调用 WdfDeviceSetDeviceInterfaceStateWdfDeviceSetDeviceInterfaceStateEx 才能启用此类接口。

当设备进行 PnP 删除时,将自动禁用所有接口。 请注意,任何设备电源状态更改或 PnP 资源重新平衡都不会更改接口的状态。

如有必要,驱动程序可以禁用和重新启用设备接口。 例如,如果驱动程序确定其设备已停止响应,则驱动程序可以调用 WdfDeviceSetDeviceInterfaceStateWdfDeviceSetDeviceInterfaceStateEx 来禁用设备的接口并禁止应用程序获取接口的新句柄。 (接口的现有句柄不受影响。) 如果设备稍后可用,驱动程序可以再次调用 WdfDeviceSetDeviceInterfaceStateWdfDeviceSetDeviceInterfaceStateEx 以重新启用接口。

接收访问设备接口的请求

当应用程序或内核模式组件请求访问驱动程序的设备接口时,框架会调用驱动程序的 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 目标。

  1. 远程驱动程序通过从 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;
        }
    
    
  2. 本地驱动程序从 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;
        }   
    
  3. 每次指定的设备接口到达或删除时,系统都会调用本地驱动程序的通知回调例程。 回调例程可以检查 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;
    }
    
  4. 从工作项回调函数中,本地驱动程序调用 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;
    }
    

注册获取设备接口到达和设备删除通知