Windows驱动开发入门-WDF初探

基本结构

WDF基本对象包括驱动对象WDFDRIVER和设备对象WDFDEVICE。

每个设备驱动都必须有与之对应的唯一的驱动对象,该对象由内核执行体I/O管理器在驱动首次加载时创建,结构定义为:

1
2
3
4
5
6
7
typedef struct _WDF_DRIVER_CONFIG {
ULONG Size;
PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd; //设备对象添加例程
PFN_WDF_DRIVER_UNLOAD EvtDriverUnload; //卸载例程
ULONG DriverInitFlags; //初始化标识符
ULONG DriverPoolTag; //调试器显示的内存表示 4字节
} WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;

驱动至少创建一个设备对象,每个设备对象包含指向下一个对象的指针,形成一个设备链。设备对象是能使软件操作硬件的数据结构,包括位于设备堆栈最底层的物理设备对象PDO,和位于PDO之上的功能设备对象FDO。每类总线都有与之对应的驱动,总线驱动检测到新设备时,PnP管理器创建一个PDO,驱动负责创建FDO并挂载到PDO上。过滤设备对象FiDO位于堆栈中FDO的上层或下层。WDF中设备对象由WdfDeviceCreate创建:

1
2
3
4
5
6
7
8
9
#include <fltKernel.h>
#include <wdf.h>
#include <wdfdriver.h>
#include <wdfrequest.h>
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDeviceCreate(
_Inout_ PWDFDEVICE_INIT* DeviceInit, //EvtDriverDeviceAdd例程入口参数
_In_opt_ PWDF_OBJECT_ATTRIBUTES DeviceAttributes, //对象属性
_Out_ WDFDEVICE* Device //设备对象句柄
);

一个基本的DriverEntry例程为:

1
2
3
4
5
6
7
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_DRIVER_CONFIG_INIT(&config,EvtDeviceAdd);
status=WdfDriverCreate(DriverObject,RegistryPath,WDF_NO_OBJECT_ATTRIBUTES,&config,WDF_NO_HANDLE);
return status;
};

驱动初始化完成后,PnP管理器用DeviceAdd例程来完成驱动锁控制设备的初始化工作,包括创建设备对象、创建I/O队列、设置GUID接口、设置事件回调例程等,原型如下:

1
2
3
4
5
typedef EVT_WDF_DRIVER_DEVICE_ADD *PFN_WDF_DRIVER_DEVICE_ADD;
typedef _Function_class_(EVT_WDF_DRIVER_DEVICE_ADD) _IRQL_requires_same_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS EVT_WDF_DRIVER_DEVICE_ADD(
_In_ WDFDRIVER Driver, //设备对象句柄
_Inout_ PWDFDEVICE_INIT DeviceInit
);

一个典型的DriverEntry例程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#endif
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject,_In_ PUNICODE_STRING RegistryPath){
WDF_DRIVER_CONFIG config;
NTSTATUS status=STATUS_SUCCESS;
WDF_OBJECT_ATTRIBUTES attributes;
WPP_INIT_TRACING(DriverObject, RegistryPath);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.EvtCleanupCallback = MyEvtDriverContextCleanup;
WDF_DRIVER_CONFIG_INIT(&config,MyEvtDeviceAdd); //初始化WDF_DRIVER_CONFIG结构
config.EvtDriverUnload=MyEvtDriverUnload; //指定卸载回调函数
status = WdfDriverCreate(DriverObject,RegistryPath,&attributes,&config,WDF_NO_HANDLE); //创建框架驱动对象
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
WPP_CLEANUP(DriverObject);
return status;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}

其中WDF_DRIVER_CONFIG结构如下:

1
2
3
4
5
6
7
typedef struct _WDF_DRIVER_CONFIG {
ULONG Size; //此结构大小 单位字节
PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd; //指向驱动EvtDriverDeviceAdd回调函数的指针 安装驱动时调用
PFN_WDF_DRIVER_UNLOAD EvtDriverUnload; //指向驱动EvtDriverUnload回调函数的指针 卸载设备时调用
ULONG DriverInitFlags; //表示驱动初始化标志的一个或多个WDF_DRIVER_INIT_FLAGS类型值的位
ULONG DriverPoolTag; //默认池标记
} WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;

对于DriverPoolTag成员,标记中每个字符ASCII值必须介于0和127之间。若该值位0,则框架使用驱动内核模式服务名称的前4个字符创建默认池标记。若服务名以“WDF”开头,不区分大小写,则用后4个字符。若可用字符少于4个,则用“FxDr”。

其中WDF_DRIVER_CONFIG_INIT用于初始化驱动的WDF_DRIVER_CONFIG结构:

1
2
3
4
VOID FORCEINLINE WDF_DRIVER_CONFIG_INIT(
_Out_ PWDF_DRIVER_CONFIG Config, //要初始化的结构
_In_opt_ PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd //EvtDriverDeviceAdd回调
);

当PnP管理器报告设备存在时,驱动的EvtDriverDeviceAdd事件回调函数执行设备初始化操作,原型如下。操作成功返回STATUS_SUCCESS,否则返回Ntstatus.h中定义的错误状态值之一。总线驱动检测到硬件标识符与驱动支持的硬件标识符匹配后,框架将调用驱动的EvtDriverDeviceAdd回调函数。通过提供INF文件来指定驱动支持的硬件ID,操作系统在首次将其中一个设备连接到计算机时使用该文件来安装驱动。

1
2
3
4
5
typedef EVT_WDF_DRIVER_DEVICE_ADD *PFN_WDF_DRIVER_DEVICE_ADD;
typedef _Function_class_(EVT_WDF_DRIVER_DEVICE_ADD) _IRQL_requires_same_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS EVT_WDF_DRIVER_DEVICE_ADD(
_In_ WDFDRIVER Driver, //驱动的框架驱动对象句柄
_Inout_ PWDFDEVICE_INIT DeviceInit //框架分配结构
);

WdfDriverCreate创建一个框架驱动对象,该对象是所有其他对象的父对象。

1
2
3
4
5
6
7
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDriverCreate(
_In_ PDRIVER_OBJECT DriverObject, //指向WDM驱动对象的指针
_In_ PCUNICODE_STRING RegistryPath, //注册表路径
_In_opt_ PWDF_OBJECT_ATTRIBUTES DriverAttributes, //可选为WDF_NO_OBJECT_ATTRIBUTES
_In_ PWDF_DRIVER_CONFIG DriverConfig, //来自调用方
_Out_opt_ WDFDRIVER* Driver //接收新框架驱动对象句柄 可选为WDF_NO_HANDLE
); //成功STATUS_SUCCESS 否则错误码

WDK模板中EvtDriverDeviceAdd回调写法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, driverstudy3EvtDeviceAdd)
#endif
NTSTATUS MyEvtDeviceAdd(_In_ WDFDRIVER Driver,_Inout_ PWDFDEVICE_INIT DeviceInit){
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = MyCreateDevice(DeviceInit);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
};
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyCreateDevice)
#endif
NTSTATUS MyCreateDevice(_Inout_ PWDFDEVICE_INIT DeviceInit){
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PDEVICE_CONTEXT deviceContext;
WDFDEVICE device;
NTSTATUS status;
PAGED_CODE();
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); //初始化该结构并将对象的驱动定义的上下文信息插入结构中
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); //创建框架设备对象
if (NT_SUCCESS(status)) {
deviceContext = DeviceGetContext(device); //类型检查并返回设备上下文
deviceContext->PrivateDeviceData = 0; //初始化设备上下文
status = WdfDeviceCreateDeviceInterface(device,&GUID_DEVINTERFACE_My,NULL); //创建设备接口
if (NT_SUCCESS(status))
status = MyQueueInitialize(device); //初始化I/O包和所有队列
}
return status;
}

其中WdfDeviceCreateDeviceInterface为指定设备创建设备接口:

1
2
3
4
5
6
#include <wdfdevice.h>
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDeviceCreateDeviceInterface(
_In_ WDFDEVICE Device, //框架设备对象句柄
_In_ CONST GUID* InterfaceClassGUID, //表示设备接口类的GUID
_In_opt_ PCUNICODE_STRING ReferenceString //描述设备接口引用字符串 不能包含路径分隔符
); //成功STATUS_SUCCESS 否则错误码

创建控制设备

先用WdfControlDeviceInitAllocate分配一块内存给WDFDEVICE_INIT结构,该结构创建控制设备时用到。

1
2
3
4
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) PWDFDEVICE_INIT FORCEINLINE WdfControlDeviceInitAllocate(
_In_ WDFDRIVER Driver, //框架驱动对象句柄
_In_ CONST UNICODE_STRING* SDDLString //安全描述符定义语言SDDL表示形式
); //成功返回指向框架分配WDFDEVICE_INIT结构的指针 否则NULL

对于SDDLString参数,可用Wdmsec.h文件中定义的一些常量。一个分配内存的例子为:

1
2
3
4
5
device_init=WdfControlDeviceInitAllocate(drv,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
if(device_init==NULL){
status=STATUS_INSUFFICIENT_RESOURCES;
goto DriverEntry_Complete;
}

接下来为设备绑定一个设备名字,该名字只能被内核模式下代码看到,用户模式下看不到。

1
2
3
4
5
#define MYWDF_KDEVICE L"\\Device\\MyWDF_Device"
RtlInitUnicodeString(&ustring,MYWDF_KDEVICE);
status=WdfDeviceInitAssignName(device_init,&ustring); //将设备名称分配给设备的设备对象
if(!NT_SUCCESS(status))
goto DriverEntry_Complete;

其中WdfDeviceInitAssignName用法如下:

1
2
3
4
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDeviceInitAssignName(
_In_ PWDFDEVICE_INIT DeviceInit,
_In_opt_ PCUNICODE_STRING DeviceName //设备名称
); //成功STATUS_SUCCESS 无法分配空间来存储设备名称STATUS_INSUFFICIENT_RESOURCES

接下来给设备绑定两个回调函数,分别为EvtDeviceFileCreate和EvtFileClose。

1
2
WDF_FILEOBJECT_CONFIG_INIT(&f_cfg,EvtDeviceFileCreate,EctFileClose,NULL); //设置回调函数
WdfDeviceInitSetFileObjectConfig(device_init,&f_cfg,WDF_NO_OBJECT_ATTRIBUTE); //存入DEVICE_INIT结构

其中WDF_FILEOBJECT_CONFIG_INIT用于初始化驱动的WDF_FILEOBJECT_CONFIG结构:

1
2
3
4
5
6
VOID FORCEINLINE WDF_FILEOBJECT_CONFIG_INIT(
_Out_ PWDF_FILEOBJECT_CONFIG FileEventCallbacks,
_In_opt_ PFN_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate,
_In_opt_ PFN_WDF_FILE_CLOSE EvtFileClose,
_In_opt_ PFN_WDF_FILE_CLEANUP EvtFileCleanup
);

其中WdfDeviceInitSetFileObjectConfig用于注册事件回调函数,并设置驱动框架文件对象的配置信息:

1
2
3
4
5
_IRQL_requires_max_(DISPATCH_LEVEL) VOID FORCEINLINE WdfDeviceInitSetFileObjectConfig(
_In_ PWDFDEVICE_INIT DeviceInit,
_In_ PWDF_FILEOBJECT_CONFIG FileObjectConfig,
_In_opt_ PWDF_OBJECT_ATTRIBUTES FileObjectAttributes
);

接着初始化设备属性并创建设备:

1
2
3
4
WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);
status=WdfDeviceCreate(&device_init,&object_attribs,&control_device); //创建控制设备
if(!NT_SUCCESS(status))
goto DriverEntry_Complete;

其中WDF_OBJECT_ATTRIBUTES_INIT用于初始化驱动的WDF_OBJECT_ATTRIBUTES结构:

1
2
3
VOID FORCEINLINE WDF_OBJECT_ATTRIBUTES_INIT(
_Out_ PWDF_OBJECT_ATTRIBUTES Attributes
);

其中WdfDeviceCreate创建框架设备对象:

1
2
3
4
5
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDeviceCreate(
_Inout_ PWDFDEVICE_INIT* DeviceInit, //成功则返回NULL
_In_opt_ PWDF_OBJECT_ATTRIBUTES DeviceAttributes, //新对象属性 可为WDF_NO_OBJECT_ATTRIBUTES
_Out_ WDFDEVICE* Device //接收新框架设备对象句柄的位置
); //成功STATUS_SUCCESS

该函数失败时返回值常见的有:

返回值 含义
STATUS_INVALID_PARAMETER 提供无效设备句柄或DeviceInit句柄
STATUS_INVALID_DEVICE_STATE 驱动已为设备创建了设备对象
STATUS_INVALID_SECURITY_DESCR 没为设备对象提供名称
STATUS_INSUFFICIENT_RESOURCES 无法分配设备对象
STATUS_OBJECT_NAME_COLLISION 设备名称已存在

然后创建一个符号链接,此时用户模式代码才能找到该设备:

1
2
3
4
RtlInitUnicodeString(&ustring,MYWDF_LINKNAME);
status=WdfDeviceCreateSymbolicLink(control_device,&ustring);
if(!NT_SUCCESS(status))
goto DriverEntry_Complete;

其中WdfDeviceCreateSymbolicLink创建指向指定设备的符号链接:

1
2
3
4
_Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FORCEINLINE WdfDeviceCreateSymbolicLink(
_In_ WDFDEVICE Device, //框架设备对象句柄
_In_ PCUNICODE_STRING SymbolicLinkName //设备用户可见名称
); //成功STATUS_SUCCESS 无法分配空间来存储设备名称STATUS_INSUFFICIENT_RESOURCES

最后完成设备的创建:

1
WdfControlFinishInitializing(control_device);

实战

常规对项目进行设置,并在链接器的附加依赖项中添加“$(DDK_LIB_PATH)\wdmsec.lib”,inf文件内容如下,如需修改就把所有mydrv字样改为工程名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[Version]
Signature = "$WINDOWS NT$"
Class = System ; TODO: specify appropriate Class
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318} ; TODO: specify appropriate ClassGuid
Provider = %ManufacturerName%
CatalogFile = mydrv.cat
DriverVer = ; TODO: set DriverVer in stampinf property pages
PnpLockdown = 1
[DestinationDirs]
DefaultDestDir = 13
[SourceDisksNames]
1 = %DiskName%,,,""
[SourceDisksFiles]
mydrv.sys = 1,,
[Manufacturer]
%ManufacturerName% = Standard,NT$ARCH$.10.0...16299 ; %13% support introduced in build 16299
[Standard.NT$ARCH$.10.0...16299]
%mydrv.DeviceDesc% = mydrv_Device, Root\mydrv ; TODO: edit hw-id
[mydrv_Device.NT]
CopyFiles = File_Copy
[File_Copy]
mydrv.sys
[mydrv_Device.NT.Services]
AddService = mydrv,%SPSVCINST_ASSOCSERVICE%, mydrv_Service_Inst
[mydrv_Service_Inst]
DisplayName = %mydrv.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %13%\mydrv.sys
[mydrv_Device.NT.Wdf]
KmdfService = mydrv, mydrv_wdfsect
[mydrv_wdfsect]
KmdfLibraryVersion = $KMDFVERSION$
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
ManufacturerName = "<Your manufacturer name>" ;TODO: Replace with your manufacturer name
DiskName = "mydrv Installation Disk"
mydrv.DeviceDesc = "mydrv Device"
mydrv.SVCDESC = "mydrv Service"

主代码mydrv.cpp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <fltKernel.h>
#include <wdf.h>
#include <wdfdriver.h>
#include <wdfrequest.h>
#define MYWDF_KDEVICE L"\\Device\\MyWDF_Device"//设备名称,其他内核模式下的驱动可以使用
#define MYWDF_LINKNAME L"\\DosDevices\\MyWDF_LINK"//符号连接,这样用户模式下的程序可以使用这个驱动设备。
//声明回调
EVT_WDF_DRIVER_UNLOAD EvtDriverUnload;
EVT_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtFileClose;
EXTERN_C NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES object_attribs;
//驱动对象相关
WDF_DRIVER_CONFIG cfg;//驱动的配置
WDFDRIVER drv = NULL;//wdf framework 驱动对象
//设备对象相关
PWDFDEVICE_INIT device_init = NULL;
UNICODE_STRING ustring;
WDF_FILEOBJECT_CONFIG f_cfg;
WDFDEVICE control_device;
PDEVICE_OBJECT dev = NULL;
DbgPrint("hello,DriverEntry has started------v2-------\n");
KdPrint(("DriverEntry [start]\n"));
//初始化WDF_DRIVER_CONFIG
WDF_DRIVER_CONFIG_INIT(&cfg, NULL); //不提供AddDevice函数
cfg.DriverInitFlags = WdfDriverInitNonPnpDriver; //指定非pnp驱动
cfg.DriverPoolTag = (ULONG)'PEPU';
cfg.EvtDriverUnload = EvtDriverUnload; //指定卸载函数
status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &cfg, &drv);
if (!NT_SUCCESS(status))
goto DriverEntry_Complete;
KdPrint(("Create wdf driver object successfully\n"));
device_init = WdfControlDeviceInitAllocate(drv, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
if (device_init == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto DriverEntry_Complete;
}
RtlInitUnicodeString(&ustring, MYWDF_KDEVICE);
//将设备名字存入device_init中
status = WdfDeviceInitAssignName(device_init, &ustring);
if (!NT_SUCCESS(status))
goto DriverEntry_Complete;
KdPrint(("Device name Unicode string: %wZ (this name can only be used by other kernel mode code, like other drivers)\n", &ustring));
WDF_FILEOBJECT_CONFIG_INIT(&f_cfg, EvtDeviceFileCreate, EvtFileClose, NULL);
WdfDeviceInitSetFileObjectConfig(device_init, &f_cfg, WDF_NO_OBJECT_ATTRIBUTES);
WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);
status = WdfDeviceCreate(&device_init, &object_attribs, &control_device);
if (!NT_SUCCESS(status)) {
KdPrint(("create device failed\n"));
goto DriverEntry_Complete;
}
RtlInitUnicodeString(&ustring, MYWDF_LINKNAME);
status = WdfDeviceCreateSymbolicLink(control_device, &ustring);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create Link\n"));
goto DriverEntry_Complete;
}
KdPrint(("Create symbolic link successfully, %wZ (user mode code should use this name, like in CreateFile())\n", &ustring));
WdfControlFinishInitializing(control_device);//创建设备完成。
KdPrint(("Create device object successfully\n"));
KdPrint(("DriverEntry succeeds [end]\n"));
DriverEntry_Complete:
return status;
}
static VOID EvtDriverUnload(WDFDRIVER Driver) {
KdPrint(("unload driver\n"));
KdPrint(("Doesn't need to clean up the devices, since we only have control device here\n"));
}/* EvtDriverUnload */
VOID EvtDeviceFileCreate(__in WDFDEVICE Device, __in WDFREQUEST Request, __in WDFFILEOBJECT FileObject) {
KdPrint(("EvtDeviceFileCreate"));
WdfRequestComplete(Request, STATUS_SUCCESS);
}
VOID EvtFileClose(__in WDFFILEOBJECT FileObject) {
KdPrint(("EvtFileClose"));
}

编译可能会显示Inf2Cat失败,可设置“Use Local Time”为“是”。