wdf 编程

WDF 是基于 WDM 的 Windows 驱动编程框架,WDF 极大地方便了 Windows 驱动的开发。

1、WDF 核心

WDF(Windows Driver Frameworks),WDF 支持两种类型的驱动,一种是 KMDF(Kernel-Mode Driver Framework) 内核模式驱动框架,一种是 UMDF(User-Mode Driver Framework) 用户模式驱动框架。

UMDF 和 KMDF 的区别从他们的名字就可以看出来,一个运行在在内核模式下,一个运行在用户模式下。用户模式下的驱动是因为有些驱动

不需要使用到内核的结构体等信息,通常用来做中间层为 Win32 应用程序和 KMDF 或其他操作系统组件之间的接口,因此才产生了所谓用户模式下的驱动这一类型。它们之间最大的区别就是运行层级的区别,其他的差别也因此而生。

WDF 包含一个DriverEntry 例程和一系列事件回调函数,这些事件回调函数在基于框架的驱动使用的对象驱动模型中定义了。回调函数调用 WDF 框架导出的对象模型。

当你创建一个 WDF 驱动,通常需要做下面几件事:

  • 使用一个框架驱动对象来代表驱动

驱动的 DriverEntry 例程调用WdfDriverCreate创建一个框架驱动对象(即 Driver.c 中 WdfDriverCreate 中的 DriverObject)用来代表这个驱动。同时 WdfDriverCreate 函数为驱动注册 EvtDriverDeviceAdd 回调函数,每当 PnP(Plug and Play) 管理器报告发现了一个驱动支持的设备时,框架就会调用该回调函数。

  • 使用一个框架设备对象来支持驱动中的 PnP 和电源管理

所有驱动都需要调用WdfDeviceCreate来为每一个驱动支持的设备创建一个框架设备对象(即 Device.c 中 WdfDeviceCreate 中的 device)。这个设备可以是一块插入计算机的硬件也可以是纯软件设备。框架设备对象支持 PnP 和电源管理操作,同时驱动可以注册一个当设备加入或离开工作状态的通知事件回调函数。有关 PnP 和电源管理

  • 使用一个框架队列对象框架请求对象来支持驱动中的 I/O 操作

所有的驱动在接收来自应用或者其他驱动的 read、write 或者设备 I/O control 请求时,必须调用WdfIoQueueCreate,来创建一个框架队列对象(即 Queue.c 中 WdfIoQueueCreate 中的 queue)用来代表 I/O 队列。通常来说,驱动为每个 I/O 队列注册一个或多个请求处理器(如:EvtIoRead、EvtIoWrite 等)。当 I/O 管理器向驱动发送一个 I/O 请求,框架会为这个请求创建一个框架请求对象,并将该对象放入到一个 I/O 队列中,随后调用一个请求处理器去通知驱动这个请求可用。驱动接收到 I/O 请求之后可以 requeue、complete、cancel 或者 forward 这个请求。有关框架队列对象框架请求对象

  • 使用一个框架中断对象来处理设备中断

驱动通过调用WdfInterruptCreate为每个中断创建一个框架中断对象并注册到回调函数的方式来处理设备中断。这些回调函数通过开启或关闭中端来充当 ISR 和 DPC。有关硬件中断

  • KMDF 驱动可以使用框架的 DMA 启用对象DMA 事务对象来处理设备的 DMA(Direct Memory access)操作

如果你的 KMDF 驱动的设备支持 DMA 操作,那么驱动需要调用WdfDmaEnablerCreate函数创建一个DMA 启用对象同时调用WdfDmaTransactionCreate函数创建一个或多个DMA 事务对象DMA 事务对象定义了EvtProgramDma回调函数使硬件设备完成 DMA 操作。有关DMA 操作

  • 使用框架的 I/O 目标对象向其他驱动发送 I/O 请求

当前驱动可以通过框架的 I/O 目标对象将 I/O 请求发送给下一层的驱动。有关I/OTargets

  • KMDF 驱动可以使用框架的 WMI 提供程序对象WMI 实例对象来支持 WMI(Windows Management Instrumentation)功能

大部分支持 WMI 的 KMDF 驱动需要调用 WdfWmiInstanceCreate 函数来注册收发 WMI 数据的回调函数。有关WMI

  • 使用框架提供的同步能力

所有的的驱动都需要意识到多处理器的同步问题,并且使用框架提供的synchronization techniques函数。

  • 使用框架提供的其他对象和功能

框架提供了其他可用的对象和功能,查看WDF Support Objects获取更多信息。

2、WDF 体系结构

WDF 为驱动程序提供了基于对象的接口。框架定义

3、DriverEntry 例程

DriverEntry 是驱动加载之后调用的第一个例程,它负责初始化整个驱动。类似于main函数,可看作整个驱动的入口。

定义:

1
2
3
4
NTSTATUS DriverEntry(
  _In_ PDRIVER_OBJECT DriverObject,
  _In_ PUNICODE_STRING RegistryPath
  );

参数:

DriverObject[in]

指向驱动对象结构体的指针,用来代表一个驱动的 WDM 驱动对象。

RegistryPath[in]

指向一个 Unicode 编码字符串结构体的指针,它指定了注册表中驱动程序的 Parameters 项路径。

返回值:

成功返回 STATUS_SUCCESS,否则返回一个在 ntstatus.h 中定义的错误状态码。

备注:

和所有的 WDM 驱动程序一样,基于框架的驱动程序必须要有 DriverEntry 例程,该例程在加载驱动时调用,基于框架的驱动程序的 DriverEntry 例程必须:

  • 激活 WPP 软件跟踪

DriverEntry 需要包含一个WPP_INIT_TRACING 宏来开启软件跟踪,UMDF 和 KMDF 所使用的 WPP_INIT_TRACING 不同,但是它们都是用来注册供应商的 GUID 并且初始化软件跟踪索要使用到的结构体的。开启 WPP 之后,用户可以使用Tracelog工具开启并控制跟踪。

  • 调用 WdfDriverCreate

调用WdfDriverCreate函数使驱动程序可以使用 WDF 提供的接口(所有驱动程序都使用 DriverEntry 例程,但是调用 WdfDriverCreate 之后才能使用 WDF 框架),在调用该方法之前,驱动程序无法调用其他的框架例程。

  • 分配所有可能用到的非设备特定的系统资源和全局变量

通常而言,驱动将系统资源和单个设备相关联,因此基于框架的驱动需要在EvtDriverDeviceAdd回调中分配大多数资源,该函数在系统检测到单个设备时被调用。

由于 UMDF 驱动程序的多个实例可能由单独的 Wudfhost 实例托管,所以全局变量可能无法在 UMDF 驱动程序的所有实例中使用。

  • 从注册表获取驱动特定的参数

某些驱动需要从注册表中获取参数,这些驱动调用WdfDriverOpenParametersRegistryKey来打开包含这些参数的注册表项。

  • 提供一个返回值

注意:

对于一个 UMDF 驱动程序来说,由于它运行在用户模式下,这个框架可能分别加载多个 UMDF 驱动实例到不同的主机进程实例中,由此将导致如下结果:

  • 如果在不同主机进程中加载 UMDF 驱动程序的实例,框架可能多次调用 DriverEntry 例程。相对的,对于 KMDF 驱动,框架只调用一次它的 DriverEntry 例程;
  • 如果 UMDF 驱动在 DriverEntry 例程创建了一个全局变量,则该变量可能不可用于驱动程序的所有实例。但是 KMDF 驱动程序这么做是可以的。