使用驱动程序定义的接口

驱动程序可以定义其他驱动程序可以访问的设备特定接口。 这些 驱动程序定义的接口 可以包含一组可调用的例程和/或一组数据结构。 驱动程序通常在驱动程序定义的接口结构中提供指向这些例程和结构的指针,驱动程序会将这些例程和结构提供给其他驱动程序。

例如,如果子设备的资源列表中没有该信息,总线驱动程序可能会提供一个或多个例程,较高级别的驱动程序可以调用该例程来获取有关子设备的信息。

有关 WDK 中记录的一组驱动程序定义接口的示例,请参阅 USB 例程。 另请参阅基于框架的 toaster 示例版本。

创建接口

每个驱动程序定义的接口都由以下各项指定:

  • GUID

  • 版本号

  • 驱动程序定义的接口结构

  • 引用和取消引用例程

若要创建接口并使其可供其他驱动程序使用,基于框架的驱动程序可以使用以下步骤:

  1. 定义接口结构。

    此驱动程序定义的结构的第一个成员必须是 INTERFACE 标头结构。 其他成员可能包括接口数据和指向其他驱动程序可以调用的其他结构或例程的指针。

    驱动程序必须提供 WDF_QUERY_INTERFACE_CONFIG 结构,该结构描述已定义的接口。

    注意

    使用 WDF_QUERY_INTERFACE_CONFIG 时,WDF 不支持使用同一接口 GUID 的单个接口的多个版本。

    因此,在引入现有接口的新版本时,建议创建新的 GUID,而不是修改 INTERFACE 结构的 SizeVersion 字段。

    如果驱动程序确实使用修改后的 SizeVersion 字段重用同一接口 GUID,则驱动程序不应提供WDF_QUERY_INTERFACE_CONFIG,而应为IRP_MN_QUERY_INTERFACE提供 EvtDeviceWdmIrpPreprocess 回调例程。

  2. 调用 WdfDeviceAddQueryInterface

    WdfDeviceAddQueryInterface 方法执行以下操作:

    • 存储有关接口的信息,例如其 GUID、版本号和结构大小,以便框架可以识别另一个驱动程序对接口的请求。
    • 注册可选的 EvtDeviceProcessQueryInterfaceRequest 事件回调函数,框架在另一个驱动程序请求接口时调用该函数。

驱动程序定义接口的每个实例都与单个设备相关联,因此驱动程序通常从 EvtDriverDeviceAdd 或 EvtChildListCreateDevice 回调函数中调用 WdfDeviceAddQueryInterface

访问接口

如果驱动程序已定义接口,则另一个基于框架的驱动程序可以通过调用 WdfFdoQueryForInterface 并传递 GUID、版本号、指向结构的指针和结构大小来请求访问该接口。 框架创建 I/O 请求并将其发送到驱动程序堆栈的顶部。

驱动程序通常从 EvtDriverDeviceAdd 回调函数中调用 WdfFdoQueryForInterface。 或者,如果驱动程序必须在设备不处于工作状态时释放接口,则驱动程序可以从 EvtDevicePrepareHardware 回调函数中调用 WdfFdoQueryForInterface,并从 EvtDeviceReleaseHardware 回调函数中调用接口的取消引用例程。

如果驱动程序 A 要求驱动程序 B 提供驱动程序 B 已定义的接口,框架将处理驱动程序 B 的请求。框架验证 GUID 和版本是否表示受支持的接口,以及驱动程序 A 提供的结构大小是否足以容纳接口。

当驱动程序调用 WdfFdoQueryForInterface 时,框架创建的 I/O 请求将一直传播到驱动程序堆栈的底部。 如果简单的驱动程序堆栈由三个驱动程序(A、B 和 C)组成,并且驱动程序 A 要求接口,则驱动程序 B 和驱动程序 C 都可以支持该接口。 例如,驱动程序 B 可能会在将请求向下传递到驱动程序 C 之前填充驱动程序 A 的接口结构。驱动程序 C 可以提供 EvtDeviceProcessQueryInterfaceRequest 回调函数,用于检查接口结构的内容并可能对其进行修改。

如果驱动程序 A 需要访问驱动程序 B 的接口,而驱动程序 B 是远程 I/O 目标 (即位于其他驱动程序堆栈) 中的驱动程序,则驱动程序 A 必须调用 WdfIoTargetQueryForInterface 而不是 WdfFdoQueryForInterface

使用One-Way或Two-Way通信

可以定义提供单向通信的接口,也可以定义提供双向通信的接口。 若要指定双向通信,驱动程序将其WDF_QUERY_INTERFACE_CONFIG结构的 ImportInterface 成员设置为 TRUE

如果接口提供单向通信,并且驱动程序 A 要求驱动程序 B 的接口,则接口数据仅从驱动程序 B 流向驱动程序 A。当框架收到驱动程序 A 对支持单向通信的接口的请求时,框架会将驱动程序定义的接口值复制到驱动程序 A 的接口结构中。 然后,它会调用驱动程序 B 的 EvtDeviceProcessQueryInterfaceRequest 回调函数(如果存在),以便可以检查并可能修改接口值。

如果接口提供双向通信,则接口结构包含驱动程序 A 在向驱动程序 B 发送请求之前填充的一些成员。驱动程序 B 可以读取驱动程序 A 提供的参数值,并根据这些值选择要提供给驱动程序 A 的信息。当框架收到驱动程序 A 对支持双向通信的接口的请求时,框架会调用驱动程序 B 的 EvtDeviceProcessQueryInterfaceRequest 回调函数,以便它可以检查收到的值并提供输出值。 对于双向通信,回调函数是必需的,因为框架不会将任何接口值复制到驱动程序 A 的接口结构。

维护引用计数

每个接口必须包含一个引用函数和一个取消引用函数,这些函数递增和递减接口的引用计数。 定义接口的驱动程序在其 INTERFACE 结构中指定这些函数的地址。

当驱动程序 A 向驱动程序 B 请求接口时,框架在使接口可供驱动程序 A 使用之前调用接口的引用函数。当驱动程序 A 使用完 接口后,它必须调用接口的取消引用函数。

大多数接口的引用函数和取消引用函数可以是不执行任何操作的无操作函数。 框架提供大多数驱动程序可以使用的无操作引用计数函数 WdfDeviceInterfaceReferenceNoOpWdfDeviceInterfaceDereferenceNoOp

驱动程序必须跟踪接口的引用计数并提供实际引用和取消引用函数的唯一时间是驱动程序 A 从远程 I/O 目标请求接口 (即位于不同驱动程序堆栈) 的驱动程序。 在这种情况下,驱动程序 B (在不同的堆栈) 必须实现引用计数,以便它可以阻止在驱动程序 A 使用驱动程序 B 的接口时删除其设备。

如果要设计定义接口的驱动程序 B,则必须确定是否将从其他驱动程序堆栈访问驱动程序的接口。 (驱动程序 B 无法确定对其接口的请求是来自本地驱动程序堆栈还是来自远程堆栈。) 如果驱动程序将支持来自远程堆栈的接口请求,则驱动程序必须实现引用计数。

如果要设计访问远程 I/O 目标上的接口的驱动程序 A,则驱动程序必须提供 EvtIoTargetQueryRemove 回调函数,该回调函数在驱动程序 B 的设备即将删除时释放接口,提供 EvtIoTargetRemoveComplete 回调函数(在驱动程序 B 的设备意外删除时释放接口)和 EvtIoTargetRemoveCanceled 如果取消尝试删除设备,则重新获取接口的回调函数。