WDF框架学习

WDF是什么?

Windows Driver Framework (WDF) 是微软提供的一套用于开发Windows设备驱动程序的框架。WDF分为两个子框架:

KMDF (Kernel-Mode Driver Framework):用于开发内核模式的驱动程序。
UMDF (User-Mode Driver Framework):用于开发用户模式的驱动程序。
WDF框架简化了驱动程序开发的复杂性,提供了丰富的API和工具,使得开发者能够更专注于业务逻辑的实现,而不是底层驱动的细节。

总之,将WDF理解为对WDM做了封装,类似于SDK和MFC之间的关系

第一节:第一个WFP驱动程序:

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
#include <ntddk.h>
#include <wdf.h>

#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)

void MyDriverUnload(
_In_ WDFDRIVER Driver
)
{
Driver;
// 释放驱动程序占用的自定义资源
// 例如,释放全局变量、关闭句柄等

// 注意:WDF 框架会自动释放 WDF 对象(如设备对象、队列对象等)
// 因此不需要手动释放这些对象

// 打印调试信息(可选)
DebugPrint("MyDriverUnload: Driver is being unloaded.\n");
}


NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
WDF_DRIVER_CONFIG config;
WDFDRIVER driver;
KdBreakPoint();
// 初始化 WDF 驱动程序配置
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
config.EvtDriverUnload = MyDriverUnload;

// 创建 WDF 驱动程序对象
status = WdfDriverCreate(
DriverObject, // WDM 驱动对象
RegistryPath, // 注册表路径
WDF_NO_OBJECT_ATTRIBUTES, // 驱动程序属性(可选)
&config, // 驱动程序配置
&driver // 返回的 WDFDRIVER 句柄
);

if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("WFP驱动初始化失败!\n");
return status;
}

// 其他初始化操作
// ...
DebugPrint("WFP驱动初始化成功!\n");
return STATUS_SUCCESS;
}

相关函数介绍

WDF_DRIVER_CONFIG_INIT

WDF_DRIVER_CONFIG_INIT 是 Windows Driver Framework (WDF) 中的一个宏,用于初始化 WDF_DRIVER_CONFIG 结构体。

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;
} WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;

EvtDriverDeviceAdd作用:指定设备添加回调函数。当设备被添加到驱动程序时,WDF 框架会调用这个函数。

EvtDriverUnload:指定驱动程序卸载回调函数。当驱动程序被卸载时,WDF 框架会调用这个函数。如果不需要卸载回调函数,可以设置为 NULL。并WDF 框架会自动释放 WDF 对象(如设备对象、队列对象等,因此不需要手动释放这些对象,只需要释放掉自己用到的其他对象

DriverInitFlags
WdfDriverInitNonPnpDriver:表示这是一个非 PnP 驱动程序。
WdfDriverInitNoDispatchOverride:表示驱动程序不会覆盖默认的调度例程。
WdfDriverInitNoFormatChaining:表示驱动程序不会使用格式链。

DriverPoolTag:指定驱动程序使用的内存池标签。内存池标签是一个 4 字符的标识符,用于调试和跟踪内存分配。如果不需要自定义内存池标签,可以设置为 0。

WDF_DRIVER_CONFIG 是 Windows Driver Framework (WDF) 中的一个重要结构体,用于配置 WDF 驱动程序的行为和属性。它在 DriverEntry 函数中用于初始化驱动程序,并传递给 WdfDriverCreate 函数。

1
2
3
4
5
6
VOID
FORCEINLINE
WDF_DRIVER_CONFIG_INIT(
_Out_ PWDF_DRIVER_CONFIG Config,
_In_opt_ PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd
)

其中:**EvtDriverDeviceAdd** 如果不需要设备添加回调函数,可以传递 WDF_NO_EVENT_CALLBACK

调用时机:当该驱动有相关设备被创建,则会调用这个EvtDriverDeviceAdd

WdfDriverCreate

WdfDriverCreate 并不是创建一个新的驱动对象,而是创建一个 WDF 驱动程序对象,并将其与现有的 DriverObject 关联起来,以便使用 WDF 框架的功能。

1
2
3
4
5
6
7
NTSTATUS WdfDriverCreate(
[in] PDRIVER_OBJECT DriverObject,
[in] PCUNICODE_STRING RegistryPath,
[in, optional] PWDF_OBJECT_ATTRIBUTES DriverAttributes,
[in] PWDF_DRIVER_CONFIG DriverConfig,
[out, optional] WDFDRIVER *Driver
);

WDF_DRIVER_CONFIG 是 Windows Driver Framework (WDF) 中的一个重要结构体,用于配置 WDF 驱动程序的行为和属性。它在 DriverEntry 函数中用于初始化驱动程序,并传递给 WdfDriverCreate 函数。

介绍下_WDF_OBJECT_ATTRIBUTES 结构

1
2
3
4
5
6
7
8
9
10
typedef struct _WDF_OBJECT_ATTRIBUTES {
ULONG Size;
PFN_WDF_OBJECT_CONTEXT_CLEANUP EvtCleanupCallback;
PFN_WDF_OBJECT_CONTEXT_DESTROY EvtDestroyCallback;
WDF_EXECUTION_LEVEL ExecutionLevel;
WDF_SYNCHRONIZATION_SCOPE SynchronizationScope;
WDFOBJECT ParentObject;
size_t ContextSizeOverride;
PCWDF_OBJECT_CONTEXT_TYPE_INFO ContextTypeInfo;
} WDF_OBJECT_ATTRIBUTES, *PWDF_OBJECT_ATTRIBUTES;

其中, 如果你定义了 EvtCleanupCallbackEvtDestroyCallback,那么你需要在这些回调函数中手动释放与 WDF 对象相关的资源。 否则WDF框架会自动清除掉这些对象的资源

  • **EvtCleanupCallback**:
    • WDF 设备对象 的引用计数降为 0 时,WDF 框架会调用 EvtCleanupCallback
    • 此时设备对象仍然有效,开发人员可以访问对象的上下文内存和其他属性。
  • **EvtDestroyCallback**:
    • EvtCleanupCallback 执行完毕后,WDF 框架会调用 EvtDestroyCallback
    • 此时设备对象已经无效,开发人员不能访问对象的上下文内存或其他属性。

WdfObjectDereference用来减少WDF设备的引用计数

第二节:设备对象,文件操作,符号链接

WdfControlDeviceInitAllocate 是 Windows Driver Framework (WDF) 中的一个函数,用于为控制设备分配一个 WDFDEVICE_INIT 结构体。

1
2
3
4
PWDFDEVICE_INIT WdfControlDeviceInitAllocate(
[in] WDFDRIVER Driver,
[in] const UNICODE_STRING *SDDLString
);

SDDLString:举个例子SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX 定义了以下权限:

  • 系统(SY):完全访问。
  • 管理员组(BA):完全访问。
  • 所有用户(WD):完全访问。
  • 受限代码(RC):完全访问。

WdfDeviceInitAssignName 是 Windows 驱动程序框架(WDF)中的一个函数,用于为设备对象分配一个名称。

1
2
3
4
NTSTATUS WdfDeviceInitAssignName(
[in] PWDFDEVICE_INIT DeviceInit,
[in, optional] PCUNICODE_STRING DeviceName
);

设备对象中的驱动程序框架文件对象(WDFFILEOBJECT) 可以理解为内核驱动程序与应用层通信的桥梁。它在 WDF(Windows 驱动程序框架)中扮演了非常重要的角色,主要用于管理与设备对象相关的用户模式句柄和上下文数据。

WDF_FILEOBJECT_CONFIG_INIT 函数初始化驱动程序WDF_FILEOBJECT_CONFIG结构。

1
2
3
4
5
6
void WDF_FILEOBJECT_CONFIG_INIT(
[out] PWDF_FILEOBJECT_CONFIG FileEventCallbacks,
[in, optional] PFN_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate,
[in, optional] PFN_WDF_FILE_CLOSE EvtFileClose,
[in, optional] PFN_WDF_FILE_CLEANUP EvtFileCleanup
);

当用户模式应用程序调用 CreateFile 或内核模式组件调用 ZwCreateFile 时, WDF 会触发 EvtDeviceFileCreate

用户模式应用程序调用 CloseHandle 或内核模式组件调用 ZwClose 时,WDF 会触发EvtWdfFileCleanup

当文件对象完全关闭时,驱动程序的 EvtWdfFileClose 被调用。

WdfDeviceInitSetFileObjectConfig

该方法注册事件回调函数并设置驱动程序框架文件对象的配置信息。

1
2
3
4
5
void WdfDeviceInitSetFileObjectConfig(
[in] PWDFDEVICE_INIT DeviceInit,
[in] PWDF_FILEOBJECT_CONFIG FileObjectConfig,
[in, optional] PWDF_OBJECT_ATTRIBUTES FileObjectAttributes
);

完整代码:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <ntddk.h>
#include <wdf.h>
#include <sddl.h> // SDDL 相关定义
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)


EVT_WDF_DEVICE_FILE_CREATE EvtWdfDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtWdfFileClose;
EVT_WDF_FILE_CLEANUP EvtWdfFileCleanup;

void EvtWdfFileCleanup(
WDFFILEOBJECT FileObject
)
{
FileObject;
}


void EvtWdfFileClose(
WDFFILEOBJECT FileObject
)
{
FileObject;
}

void EvtWdfDeviceFileCreate(
WDFDEVICE Device,
WDFREQUEST Request,
WDFFILEOBJECT FileObject
)
{
Device;
Request;
FileObject;
}



void MyDriverUnload(
_In_ WDFDRIVER Driver
)
{
Driver;
// 释放驱动程序占用的自定义资源
// 例如,释放全局变量、关闭句柄等

// 注意:WDF 框架会自动释放 WDF 对象(如设备对象、队列对象等)
// 因此不需要手动释放这些对象

// 打印调试信息(可选)
DebugPrint("MyDriverUnload: Driver is being unloaded.\n");
}


NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status; //状态
WDF_DRIVER_CONFIG config; //WDF驱动配置
WDFDRIVER driver; //WDF驱动对象
WDFDEVICE Device; //WDF设备对象
PWDFDEVICE_INIT DeviceInit; //初始化设备对象的创建过程
UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\HelloWDF"); //设备对象的名字
UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\HelloWDF"); //
WDF_FILEOBJECT_CONFIG FileConfig;

KdBreakPoint();
// 初始化 WDF 驱动程序配置
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
config.EvtDriverUnload = MyDriverUnload;

// 创建 WDF 驱动程序对象
status = WdfDriverCreate(
DriverObject, // WDM 驱动对象
RegistryPath, // 注册表路径
WDF_NO_OBJECT_ATTRIBUTES, // 驱动程序属性(可选)
&config, // 驱动程序配置
&driver // 返回的 WDFDRIVER 句柄
);


DeviceInit = WdfControlDeviceInitAllocate(driver,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX);
if (DeviceInit == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto End;
}


status=WdfDeviceInitAssignName(DeviceInit, &DeviceName);
if (!NT_SUCCESS(status))
{
DebugPrint("设备分配失败!\n");
goto End;
}


//WDF_FILEOBJECT_CONFIG_INIT 函数初始化驱动程序WDF_FILEOBJECT_CONFIG结构
WDF_FILEOBJECT_CONFIG_INIT(&FileConfig, EvtWdfDeviceFileCreate, EvtWdfFileClose, EvtWdfFileCleanup);

//WdfDeviceInitSetFileObjectConfig 方法注册事件回调函数并设置驱动程序框架文件对象的配置信息。
WdfDeviceInitSetFileObjectConfig(DeviceInit, &FileConfig, WDF_NO_OBJECT_ATTRIBUTES);

status = WdfDeviceCreate(&DeviceInit,WDF_NO_OBJECT_ATTRIBUTES,&Device);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("设备创建失败\n");
goto End;
}

status = WdfDeviceCreateSymbolicLink(Device, &SymbolicLinkName);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("符号链接创建失败\n");
goto End;
}

WdfControlFinishInitializing(Device);

// 其他初始化操作
// ...
DebugPrint("WFP驱动初始化成功!\n");
return STATUS_SUCCESS;

End:
// ...
DebugPrint("WFP驱动初始化失败!\n");
return STATUS_UNSUCCESSFUL;

}

总结

  • **DriverEntry**:驱动程序的入口函数,负责初始化驱动程序。
  • **WdfDriverCreate**:创建 WDF 驱动程序对象。
  • **WdfControlDeviceInitAllocate**:分配设备初始化结构。
  • **WdfDeviceInitAssignName**:设置设备名称。
  • **WDF_FILEOBJECT_CONFIG_INIT**:初始化文件对象配置。
  • **WdfDeviceInitSetFileObjectConfig**:设置文件对象配置。
  • **WdfDeviceCreate**:创建设备对象。
  • **WdfDeviceCreateSymbolicLink**:创建符号链接。
  • **WdfControlFinishInitializing**:完成设备初始化。

第三节:编写应用程序访问WDF驱动

在第二节,我们写了对于文件的三个回调,分别是以下三个

当然要说明: 文件对象的回调函数(如 EvtWdfDeviceFileCreateEvtWdfFileCloseEvtWdfFileCleanup)主要用于管理文件对象的生命周期,而不是直接处理 I/O 请求(如 ReadFileWriteFile)。WDF 使用队列(Queue)机制来处理 I/O 请求,而不是像 WDM 那样直接通过回调函数处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
void EvtWdfFileCleanup(
WDFFILEOBJECT FileObject
)

void EvtWdfFileClose(
WDFFILEOBJECT FileObject
)

void EvtWdfDeviceFileCreate(
WDFDEVICE Device,
WDFREQUEST Request,
WDFFILEOBJECT FileObject
)

其中,Create回调里面有一个Request,这个我们必须处理它

处理的方式还是比较多的,例如

1.直接完成请求:(当然你也可以验证资质)

1
2
// 直接完成请求
WdfRequestComplete(Request, STATUS_SUCCESS);

2.初始化文件对象上下文

1
2
3
4
5
6
7
8
9
10
11
12
// 获取文件对象的上下文数据
PFILE_CONTEXT fileContext = FileObjectGetContext(FileObject);

// 初始化上下文数据
fileContext->Buffer = ExAllocatePoolWithTag(NonPagedPool, 1024, 'BufT');
if (fileContext->Buffer == NULL) {
WdfRequestComplete(Request,STATUS_INSUFFICIENT_RESOURCES);
return;
}

// 完成请求
WdfRequestComplete(Request, STATUS_SUCCESS);

3.异步完成请求: 如果设备对象的打开操作需要较长时间(例如等待某个资源),可以将请求标记为挂起,并在操作完成后异步完成请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void EvtWdfDeviceFileCreate(
WDFDEVICE Device,
WDFREQUEST Request,
WDFFILEOBJECT FileObject
) {
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(FileObject);

// 将请求标记为挂起
WdfRequestMarkPending(Request);

// 异步完成请求(例如在某个工作线程中)
MyAsyncOpenDevice(Request);
}

void MyAsyncOpenDevice(WDFREQUEST Request) {
// 模拟异步操作
KeDelayExecutionThread(KernelMode, FALSE, 1000);

// 完成请求
WdfRequestComplete(Request, STATUS_SUCCESS);
}

winobj可以看到全局的符号链接:

1736737321105

直接在应用层去访问这个设备

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
#include <windows.h>
#include <stdio.h>

#define DEVICE_NAME L"\\\\.\\HelloWDF" // 设备的符号链接名称

int main() {
system("pause");
HANDLE hDevice = INVALID_HANDLE_VALUE;
BOOL result = FALSE;
DWORD bytesReturned = 0;
char outputBuffer[100] = { 0 };

// 打开设备
hDevice = CreateFile(
DEVICE_NAME, // 设备名称
GENERIC_READ | GENERIC_WRITE, // 访问权限
0, // 共享模式(0 表示独占)
NULL, // 安全属性
OPEN_EXISTING, // 创建选项
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL // 模板文件句柄
);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device. Error: %d\n", GetLastError());
return 1;
}
return 0;
}

可以看到成功断下来了

1736737692878

第四节:实现WDF的IO队列

本节将实现对文件设备的控制操作

首先我们先建立一个IO队列的配置

1
WDF_IO_QUEUE_CONFIG       ioConfig

然后用WDF_IO_QUEUE_CONFIG_INIT来初始化这个ioConfig

1
2
3
4
void WDF_IO_QUEUE_CONFIG_INIT(
[out] PWDF_IO_QUEUE_CONFIG Config,
[in] WDF_IO_QUEUE_DISPATCH_TYPE DispatchType
);

其中 DispatchType枚举值是这样的

描述
WdfIoQueueDispatchInvalid 无效的分发类型(仅用于内部验证)。
WdfIoQueueDispatchSequential 顺序分发:I/O 请求按顺序处理,一次只处理一个请求。
WdfIoQueueDispatchParallel 并行分发:I/O 请求可以同时处理,适用于高性能场景。
WdfIoQueueDispatchManual 手动分发:驱动程序需要手动从队列中取出请求并处理。
WdfIoQueueDispatchMax 枚举的最大值(仅用于内部验证)。

_WDF_IO_QUEUE_CONFIG结构中,我们可以发现很多WDM熟悉的东西,例如READ,WRITE,IO_DEVICE_CONTROL,这就是对应的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _WDF_IO_QUEUE_CONFIG {
ULONG Size;
WDF_IO_QUEUE_DISPATCH_TYPE DispatchType;
WDF_TRI_STATE PowerManaged;
BOOLEAN AllowZeroLengthRequests;
BOOLEAN DefaultQueue;
PFN_WDF_IO_QUEUE_IO_DEFAULT EvtIoDefault;
PFN_WDF_IO_QUEUE_IO_READ EvtIoRead;
PFN_WDF_IO_QUEUE_IO_WRITE EvtIoWrite;
PFN_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControl;
PFN_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoInternalDeviceControl;
PFN_WDF_IO_QUEUE_IO_STOP EvtIoStop;
PFN_WDF_IO_QUEUE_IO_RESUME EvtIoResume;
PFN_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE EvtIoCanceledOnQueue;
union {
struct {
ULONG NumberOfPresentedRequests;
} Parallel;
} Settings;
WDFDRIVER Driver;
} WDF_IO_QUEUE_CONFIG, *PWDF_IO_QUEUE_CONFIG;

这里做一个表格帮助理解

WDF 回调函数 对应三环 API 描述
EvtIoDefault 无直接对应 默认处理逻辑,覆盖未分类的请求。
EvtIoRead ReadFile, ReadFileEx 处理读请求。
EvtIoWrite WriteFile, WriteFileEx 处理写请求。
EvtIoDeviceControl DeviceIoControl 处理用户模式的 IOCTL 请求。
EvtIoInternalDeviceControl 无直接对应 处理内核模式的 IOCTL 请求。
EvtIoStop 无直接对应 恢复队列请求
EvtIoResume 无直接对应 恢复队列请求
EvtIoCanceledOnQueue CancelIo, CancelIoEx 用户取消队列中未完成的请求时调用。

总结一下顺序

  1. 设备初始化
    • 调用 WdfDeviceInitAllocateWdfControlDeviceInitAllocate 分配并初始化 WDFDEVICE_INIT 结构。
    • 设置设备属性(如设备名称、安全描述符等)。
  2. 文件对象配置初始化
    • 使用 WDF_FILEOBJECT_CONFIG_INIT 初始化 WDF_FILEOBJECT_CONFIG 结构。
    • 设置文件对象的回调函数(如 EvtWdfDeviceFileCreateEvtWdfFileClose 等)。
  3. 设置文件对象配置
    • 调用 WdfDeviceInitSetFileObjectConfig 将文件对象配置与设备初始化结构关联。
  4. 创建设备对象
    • 调用 WdfDeviceCreate 创建设备对象。
  5. 创建 I/O 队列
    • 在设备对象创建成功后,调用 WdfIoQueueCreate 创建 I/O 队列。
  6. 完成设备创建
    WdfControlFinishInitializing(Device);

本节完整驱动程序:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include <ntddk.h>
#include <wdf.h>
#include <sddl.h> // SDDL 相关定义
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)


EVT_WDF_DEVICE_FILE_CREATE EvtWdfDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtWdfFileClose;
EVT_WDF_FILE_CLEANUP EvtWdfFileCleanup;


VOID m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
)
{
Queue;
Request;
OutputBufferLength;
InputBufferLength;
IoControlCode;
DebugPrint("IO_DEVICE_CONTROL\n");
WdfRequestComplete(Request, STATUS_SUCCESS);
}

void EvtWdfFileCleanup(
WDFFILEOBJECT FileObject
)
{
FileObject;
DebugPrint("clean_up\n");
}


void EvtWdfFileClose(
WDFFILEOBJECT FileObject
)
{
FileObject;
DebugPrint("close\n");
}

void EvtWdfDeviceFileCreate(
WDFDEVICE Device,
WDFREQUEST Request,
WDFFILEOBJECT FileObject
)
{
Device;
Request;
FileObject;

DebugPrint("file_create\n");
WdfRequestComplete(Request, STATUS_SUCCESS);

}



void MyDriverUnload(
_In_ WDFDRIVER Driver
)
{
Driver;
// 释放驱动程序占用的自定义资源
// 例如,释放全局变量、关闭句柄等

// 注意:WDF 框架会自动释放 WDF 对象(如设备对象、队列对象等)
// 因此不需要手动释放这些对象

// 打印调试信息(可选)
DebugPrint("MyDriverUnload: Driver is being unloaded.\n");
}


NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status; //状态
WDF_DRIVER_CONFIG config; //WDF驱动配置
WDFDRIVER driver; //WDF驱动对象
WDFDEVICE Device; //WDF设备对象
PWDFDEVICE_INIT DeviceInit; //初始化设备对象的创建过程
UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\HelloWDF"); //设备对象的名字
UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\HelloWDF"); //
WDF_FILEOBJECT_CONFIG FileConfig; //文件对象的配置信息
WDF_IO_QUEUE_CONFIG ioConfig; //IO队列的配置信息
WDFQUEUE Queue; //这是IO队列句柄

KdBreakPoint();
// 初始化 WDF 驱动程序配置
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
config.EvtDriverUnload = MyDriverUnload;

// 创建 WDF 驱动程序对象
status = WdfDriverCreate(
DriverObject, // WDM 驱动对象
RegistryPath, // 注册表路径
WDF_NO_OBJECT_ATTRIBUTES, // 驱动程序属性(可选)
&config, // 驱动程序配置
&driver // 返回的 WDFDRIVER 句柄
);


DeviceInit = WdfControlDeviceInitAllocate(driver,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX);
if (DeviceInit == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto End;
}


status=WdfDeviceInitAssignName(DeviceInit, &DeviceName);
if (!NT_SUCCESS(status))
{
DebugPrint("设备分配失败!\n");
goto End;
}


//WDF_FILEOBJECT_CONFIG_INIT 函数初始化驱动程序WDF_FILEOBJECT_CONFIG结构
WDF_FILEOBJECT_CONFIG_INIT(&FileConfig, EvtWdfDeviceFileCreate, EvtWdfFileClose, EvtWdfFileCleanup);

//WdfDeviceInitSetFileObjectConfig 方法注册事件回调函数并设置驱动程序框架文件对象的配置信息。
WdfDeviceInitSetFileObjectConfig(DeviceInit, &FileConfig, WDF_NO_OBJECT_ATTRIBUTES);

status = WdfDeviceCreate(&DeviceInit,WDF_NO_OBJECT_ATTRIBUTES,&Device);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("设备创建失败\n");
goto End;
}



WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioConfig, WdfIoQueueDispatchSequential);

ioConfig.EvtIoDeviceControl = m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL;

status = WdfIoQueueCreate(Device, &ioConfig, WDF_NO_OBJECT_ATTRIBUTES, &Queue);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("设备队列创建失败\n");
goto End;
}


status = WdfDeviceCreateSymbolicLink(Device, &SymbolicLinkName);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("符号链接创建失败\n");
goto End;
}

WdfControlFinishInitializing(Device);
// 其他初始化操作
// ...
DebugPrint("WFP驱动初始化成功!\n");
return STATUS_SUCCESS;

End:
// ...
DebugPrint("WFP驱动初始化失败!\n");
return STATUS_UNSUCCESSFUL;

}

应用层:

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
#include <windows.h>
#include <stdio.h>

#define DEVICE_NAME L"\\\\.\\HelloWDF" // 设备的符号链接名称

int main() {
system("pause");
HANDLE hDevice = INVALID_HANDLE_VALUE;
BOOL result = FALSE;
DWORD bytesReturned = 0;
//char outputBuffer[100] = { 0 };
char inputBuffer[100] = "Test input data"; // 输入缓冲区示例
char outputBuffer[100] = { 0 }; // 输出缓冲区
// 打开设备
hDevice = CreateFile(
DEVICE_NAME, // 设备名称
GENERIC_READ | GENERIC_WRITE, // 访问权限
0, // 共享模式(0 表示独占)
NULL, // 安全属性
OPEN_EXISTING, // 创建选项
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL // 模板文件句柄
);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device. Error: %d\n", GetLastError());
return 1;
}


// 调用 DeviceIoControl
result = DeviceIoControl(
hDevice, // 设备句柄
NULL, // 控制代码
inputBuffer, // 输入缓冲区
sizeof(inputBuffer), // 输入缓冲区大小
outputBuffer, // 输出缓冲区
sizeof(outputBuffer), // 输出缓冲区大小
&bytesReturned, // 返回的字节数
NULL // 重叠结构(非异步调用时为 NULL)
);
return 0;
}

实现效果:

1736741233393

第五节:从0实现支持pnp的WDF驱动

什么是Pnp,为什么需要Pnp

我们来讨论下pnp

首先什么是pnp? PnP(Plug and Play,即插即用)是一种功能,让操作系统能够自动检测和配置新硬件设备,而无需用户干预。PnP 驱动程序会响应硬件的添加、移除、资源分配和电源管理事件。

我们来看看WDM是如何实现Pnp的

在 Windows 驱动模型 (WDM) 中,支持 PnP 是非常重要的特性。PnP 驱动主要通过响应系统发送的 IRP_MN_XXX 类型的 I/O 请求包 (IRP) 来实现对设备的动态管理,例如设备启动 (IRP_MN_START_DEVICE)、设备移除 (IRP_MN_REMOVE_DEVICE) 和设备停止 (IRP_MN_STOP_DEVICE)

通过这些NT驱动没有的IRP,实现了Pnp

那么在WDF中,对Pnp进行了进一步的封装,WDF 使用回调函数(如 EvtDeviceAddEvtDevicePrepareHardware)来处理 PnP 和电源管理事件,而不是直接处理 IRP。

为什么我们需要Pnp?为什么不把创建对象,也就是WdfDeviceCreate直接写在DriverEntry

这里要分控制设备和功能设备

控制设备
对于控制设备(如非即插即用设备或虚拟设备),可以在 DriverEntry 或类似的初始化函数中调用 WdfDeviceCreate 创建设备对象,因为这些设备与硬件无关,不依赖 PnP 机制。

功能设备
对于功能设备,系统会通过 PnP 机制向驱动程序通知设备的到达事件,驱动程序在 EvtDriverDeviceAdd 中为设备创建相应的 WDFDEVICE,并初始化硬件资源。

我们现在写的都是创建的虚拟设备,实际上并没有硬件插入的

我们可以这样想象一个场景:

假设你正在为一款即插即用的PCI网卡编写一个驱动程序。这张网卡可能有多个型号,且可能有多个实例插入到同一台电脑上(例如插了两块相同型号的网卡)。 如果说是将WdfDeviceCreate 写在DriverEntry,但是驱动已经调用过一遍DriverEntry,不能再调用了,这下就没办法生成一个新的设备对象,但是如果写在EvtDriverDeviceAdd,就可以在系统感知到设备插入自动调用生成一个设备对象,这就是为什么我们需要pnp

实现的示例代码

1736777494168

Device.h,Device.c:这里是实现当有设备插入,自动创建设备对象并设置回调函数的相关实现

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#ifndef DEVICE_H
#define DEVICE_H
#include <ntddk.h>
#include <wdf.h>
NTSTATUS m_EVT_WDF_DRIVER_DEVICE_ADD(
_In_
WDFDRIVER Driver,
_Inout_
PWDFDEVICE_INIT DeviceInit
);
#endif


#include <ntddk.h>
#include <wdf.h>
#include "Device.h"
#include "Queue.h"
#include <initguid.h>

#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)
DEFINE_GUID(DEVICEINTERFACE, 0x8C9528E5, 0xE99D, 0xDBE9, 0xA4, 0x10, 0xF1, 0x7C, 0xDB, 0x96, 0x66, 0x0B);

void EvtWdfFileCleanup(
WDFFILEOBJECT FileObject
)
{
FileObject;
DebugPrint("clean_up\n");
}


void EvtWdfFileClose(
WDFFILEOBJECT FileObject
)
{
FileObject;
DebugPrint("close\n");
};


void EvtWdfDeviceFileCreate(
WDFDEVICE Device,
WDFREQUEST Request,
WDFFILEOBJECT FileObject
)
{
Device;
Request;
FileObject;

DebugPrint("file_create\n");
WdfRequestComplete(Request, STATUS_SUCCESS);
}

NTSTATUS m_EVT_WDF_DRIVER_DEVICE_ADD(
_In_
WDFDRIVER Driver,
_Inout_
PWDFDEVICE_INIT DeviceInit
)
{
Driver;
DeviceInit;
NTSTATUS status=STATUS_SUCCESS;
WDF_FILEOBJECT_CONFIG FileConfig; //文件对象的配置信息
WDFDEVICE Device; //WDF设备对象
WDF_IO_QUEUE_CONFIG IoQueueConfig;

UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\HelloWDF");
status = WdfDeviceInitAssignName(DeviceInit,&DeviceName);
if (!NT_SUCCESS(status))
{
DebugPrint("设备分配失败!\n");
goto End;
}


//WDF_FILEOBJECT_CONFIG_INIT 函数初始化驱动程序WDF_FILEOBJECT_CONFIG结构
WDF_FILEOBJECT_CONFIG_INIT(&FileConfig, EvtWdfDeviceFileCreate, EvtWdfFileClose, EvtWdfFileCleanup);

//WdfDeviceInitSetFileObjectConfig 方法注册事件回调函数并设置驱动程序框架文件对象的配置信息。
WdfDeviceInitSetFileObjectConfig(DeviceInit, &FileConfig, WDF_NO_OBJECT_ATTRIBUTES);

status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &Device);
if (!NT_SUCCESS(status)) {
// 处理错误
DebugPrint("设备创建失败\n");
goto End;
}

//设置IO队列
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&IoQueueConfig, WdfIoQueueDispatchSequential);

IoQueueConfig.EvtIoDeviceControl = m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL; //添加IO_DEVICE_CONTROL回调


// 创建 I/O 队列
WDFQUEUE IoQueue = NULL; // 定义一个 WDFQUEUE 变量
status = WdfIoQueueCreate(Device, &IoQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &IoQueue);
if (!NT_SUCCESS(status)) {
DebugPrint("Failed to create I/O queue: 0x%X\n", status);
goto End;
}


WdfDeviceCreateDeviceInterface(Device, &DEVICEINTERFACE, NULL); //为设备注册一个设备接口

WdfControlFinishInitializing(Device); //完成设备的初始化

DebugPrint("Wdf_Driver_Add\n");

return status;


End:
status = STATUS_UNSUCCESSFUL;
return status;

}

可以发现这里并没有使用建立符号链接,而是选择了为设备注册一个设备接口,连设备名都不需要了

WdfDeviceCreateDeviceInterface

用途:为设备注册一个设备接口,供用户模式应用程序通过设备接口 GUID 进行访问。

特性 WdfDeviceCreateDeviceInterface WdfDeviceCreateSymbolicLink
用途 动态设备发现和访问 提供固定路径直接访问设备
PnP 支持 完全支持 需要开发者手动管理插拔、状态变化
设备实例管理 可用于多个设备实例,每个实例动态注册接口 GUID 一个符号链接通常绑定一个设备实例
用户模式访问 用户程序通过接口 GUID 枚举和打开设备 用户程序通过固定符号链接路径打开设备
API 兼容性 使用 SetupDi 系列 API 使用传统 Win32 API (CreateFile)
适用场景 现代驱动、支持动态枚举 传统驱动、调试用

Queue.h和Queue.c 这里是实现具体回调函数

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
#ifndef  QUEUE_H
#define QUEUE_H
#include <ntddk.h>
#include <wdf.h>
VOID m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
);
#endif // ! QUEUE_H


#include <ntddk.h>
#include <wdf.h>
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)

VOID m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
)
{
Queue;
Request;
OutputBufferLength;
InputBufferLength;
IoControlCode;
DebugPrint("IO_DEVICE_CONTROL\n");
WdfRequestComplete(Request, STATUS_SUCCESS);
}

DriverEntry:

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
#include <ntddk.h>
#include <wdf.h>
#include "Device.h"
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)


NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status; //状态
WDF_DRIVER_CONFIG config; //WDF驱动配置
WDFDRIVER driver; //WDF驱动对象
KdBreakPoint();
// 初始化 WDF 驱动程序配置
WDF_DRIVER_CONFIG_INIT(&config, m_EVT_WDF_DRIVER_DEVICE_ADD);
config.EvtDriverUnload = MyDriverUnload;

// 创建 WDF 驱动程序对象
status = WdfDriverCreate(
DriverObject, // WDM 驱动对象
RegistryPath, // 注册表路径
WDF_NO_OBJECT_ATTRIBUTES, // 驱动程序属性(可选)
&config, // 驱动程序配置
&driver // 返回的 WDFDRIVER 句柄
);

if (!NT_SUCCESS(status))
{
DebugPrint("注册驱动的框架对象失败\n");
return status;
}

return status;
}

注意,相比于前几节,如果在WDF_DRIVER_CONFIG_INIT设置了EVT_DRIVER_DEVICE_ADD,那么这里一定不能添加config.DriverInitFlags |= WdfDriverInitNonPnpDriver;

另外

  • 对于即插即用驱动程序(PnP驱动程序),通常不需要指定 EvtDriverUnload 回调函数,因为即插即用驱动程序的卸载是由设备管理器(Device Manager)和操作系统自动处理的。
  • 即插即用驱动程序的卸载通常是通过删除设备节点或禁用设备来触发的,而不是通过显式的卸载函数。

加载和卸载驱动:

平时我们都是用KmdManager直接加载驱动,但是这样会有一个不好的点,就是仅仅添加驱动,但是不会创建设备,所以我们不好直接用它来测试pnp驱动

所以我们用hdwwiz

使用 hdwwiz 添加 INF 文件时,Windows 会执行以下操作:

  1. 解析 INF 文件,获取设备的硬件 ID 和驱动程序信息。
  2. 创建设备节点(Device Node)。
  3. 将设备节点与已加载的驱动程序关联。
  4. 调用驱动程序的 AddDevice 例程来创建设备对象。

关键点

  • 如果驱动程序已经加载(例如通过 sc createnet start),Windows 不会重新加载驱动程序,因此不会再次调用 DriverEntry
  • 如果驱动程序未加载,Windows 会先加载驱动程序(调用 DriverEntry),然后再调用 AddDevice

在设备管理器这里可以选择禁用设备和卸载设备

1736824974600

第六节:使用Pnp的应用程序

示例代码如下:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <windows.h>
#include <setupapi.h>
#include <tchar.h>
#include <stdio.h>
#include <initguid.h>

// 替换为你的GUID
DEFINE_GUID(DEVICEINTERFACE, 0x8C9528E5, 0xE99D, 0xDBE9, 0xA4, 0x10, 0xF1, 0x7C, 0xDB, 0x96, 0x66, 0x0B);
void GetDeviceInterfacePath()
{
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
DWORD requiredSize = 0;
BOOL success;


system("pause");

// 获取设备信息集合
deviceInfoSet = SetupDiGetClassDevs(
&DEVICEINTERFACE,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

/*
DIGCF_PRESENT:仅返回当前连接到系统的设备。
DIGCF_DEVICEINTERFACE:返回设备接口类的设备信息集。
*/

if (deviceInfoSet == INVALID_HANDLE_VALUE) {
printf("SetupDiGetClassDevs failed\n");
return;
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);//设备接口数据

// 枚举设备接口
success = SetupDiEnumDeviceInterfaces(
deviceInfoSet,
NULL,
&DEVICEINTERFACE,
0,
&deviceInterfaceData);

if (!success) {
printf("SetupDiEnumDeviceInterfaces failed\n");
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return;
}

// 获取接口详细信息所需缓冲区大小
SetupDiGetDeviceInterfaceDetail(
deviceInfoSet,
&deviceInterfaceData,
NULL,
0,
&requiredSize,
NULL);

deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize);
deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

// 获取设备接口详细信息
success = SetupDiGetDeviceInterfaceDetail(
deviceInfoSet,
&deviceInterfaceData,
deviceInterfaceDetailData,
requiredSize,
NULL,
NULL);

if (!success) {
printf("SetupDiGetDeviceInterfaceDetail failed\n");
free(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return;
}

printf("Device Path: %s\n", deviceInterfaceDetailData->DevicePath);

// 使用设备路径(例如,通过 CreateFile 打开设备)
HANDLE deviceHandle = CreateFile(
deviceInterfaceDetailData->DevicePath,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

if (deviceHandle == INVALID_HANDLE_VALUE) {
printf("Failed to open device\n");
}
else {
printf("Device opened successfully\n");
CloseHandle(deviceHandle);
}

free(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}


int main()
{
GetDeviceInterfacePath();
return 0;
}

成功触发pnp的回调

1736824535036

第七节:WDF中文数字转换驱动

关于IO控制码的编写:

1
2
#define CTL_CODE(DeviceType, Function, Method, Access) 
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))

**DeviceType**: 指定设备类型 ,可以是预定义的设备类型(例如 FILE_DEVICE_DISKFILE_DEVICE_KEYBOARD 等)。如果设备类型未知,可以使用 FILE_DEVICE_UNKNOWN

Function:指定功能代码。通常从 0x800 开始,以避免与系统保留的功能代码冲突。

Method:作用:指定数据传输方法。
取值:
METHOD_BUFFERED:使用缓冲 I/O(数据通过系统缓冲区传递)。
METHOD_IN_DIRECT:使用直接 I/O(输入数据通过直接内存访问传递)。
METHOD_OUT_DIRECT:使用直接 I/O(输出数据通过直接内存访问传递)。
METHOD_NEITHER:不使用系统缓冲区,直接传递用户模式指针。

**Access**:作用:指定访问权限。
取值:
FILE_ANY_ACCESS:允许任何访问。
FILE_READ_ACCESS:允许读访问。
FILE_WRITE_ACCESS:允许写访问。
FILE_READ_ACCESS | FILE_WRITE_ACCESS:允许读写访问。

例如这样定义一个IOCTL_CODE

1
#define IOCTL_TEST CTL_CODE(FILE_DEVICE_UNKNOWN,0X800,METHOD_BUFFERED,FILE_ANY_ACCESS)

IO_DEVICE_CONTROL处理回调函数

1
2
3
4
5
6
7
8
9
10
11
12
VOID m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
);

检索 I/O 请求的输出缓冲区。

1
2
3
4
5
6
NTSTATUS WdfRequestRetrieveOutputBuffer(
[in] WDFREQUEST Request,
[in] size_t MinimumRequiredSize,
[out] PVOID *Buffer,
[out, optional] size_t *Length
);

其中MinimumRequiredSize 参数用于指定驱动程序需要的最小输出缓冲区大小 ,如果用户层提供的缓冲区大小小于 MinimumRequiredSizeWdfRequestRetrieveOutputBuffer 会返回 STATUS_BUFFER_TOO_SMALL,驱动程序可以拒绝处理请求。

WdfRequestRetrieveInputBuffer 也是如此

应用层代码

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <windows.h>
#include <stdio.h>
#include <setupapi.h>
#include <initguid.h>

// 定义设备接口 GUID
DEFINE_GUID(DEVICEINTERFACE, 0x8C9528E5, 0xE99D, 0xDBE9, 0xA4, 0x10, 0xF1, 0x7C, 0xDB, 0x96, 0x66, 0x0B);

#define IOCTL_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

BOOL GetDevicePath(LPGUID InterfaceGuid, char* DevicePath, size_t DevicePathSize) {
HDEVINFO deviceInfoSet = SetupDiGetClassDevsA(InterfaceGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
printf("SetupDiGetClassDevsA failed: %lu\n", GetLastError());
return FALSE;
}

SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, InterfaceGuid, 0, &deviceInterfaceData)) {
printf("SetupDiEnumDeviceInterfaces failed: %lu\n", GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return FALSE;
}

DWORD requiredSize = 0;
SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, NULL, 0, &requiredSize, NULL);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
printf("SetupDiGetDeviceInterfaceDetailA size query failed: %lu\n", GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return FALSE;
}

PSP_DEVICE_INTERFACE_DETAIL_DATA_A deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA_A)malloc(requiredSize);
if (!deviceInterfaceDetailData) {
printf("Memory allocation failed.\n");
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return FALSE;
}

deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);

if (!SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, deviceInterfaceDetailData, requiredSize, NULL, NULL)) {
printf("SetupDiGetDeviceInterfaceDetailA failed: %lu\n", GetLastError());
free(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return FALSE;
}

strncpy(DevicePath, deviceInterfaceDetailData->DevicePath, DevicePathSize - 1);
DevicePath[DevicePathSize - 1] = '\0';

free(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return TRUE;
}

int main() {
system("pause");
char devicePath[MAX_PATH] = { 0 };

// 获取设备路径
if (!GetDevicePath((LPGUID)&DEVICEINTERFACE, devicePath, sizeof(devicePath))) {
printf("Failed to get device path.\n");
return 1;
}

printf("Device Path: %s\n", devicePath);

// 打开设备
HANDLE hDevice = CreateFileA(
devicePath,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device: %lu\n", GetLastError());
return 1;
}

while (1)
{
// 准备输入和输出缓冲区
char inputBuffer[0x20];
char outputBuffer[256] = { 0 }; // 输出汉字结果
DWORD bytesReturned;
scanf("%s", inputBuffer);
// 发送 IOCTL 请求
BOOL result = DeviceIoControl(
hDevice,
IOCTL_TEST,
inputBuffer,
sizeof(inputBuffer),
outputBuffer,
sizeof(outputBuffer),
&bytesReturned,
NULL
);

if (result) {
printf("Driver response: %s\n", outputBuffer);
}
else {
printf("DeviceIoControl failed: %lu\n", GetLastError());
}
}
// 关闭设备句柄
CloseHandle(hDevice);
return 0;
}

驱动代码

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <ntddk.h>
#include <wdf.h>
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)

#define IOCTL_TEST CTL_CODE(FILE_DEVICE_UNKNOWN,0X800,METHOD_BUFFERED,FILE_ANY_ACCESS)


VOID m_EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
) {
UNREFERENCED_PARAMETER(Queue);

NTSTATUS status = STATUS_SUCCESS;
PVOID inputBuffer = NULL;
PVOID outputBuffer = NULL;

// 数字到汉字的映射表
const char* digitToChinese[] = {
"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"
};

char temp[0x100];

switch (IoControlCode) {
case IOCTL_TEST:
{
// 获取输入缓冲区
status = WdfRequestRetrieveInputBuffer(Request, InputBufferLength, &inputBuffer, NULL);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to retrieve input buffer: 0x%X\n", status));
break;
}

// 获取输出缓冲区
status = WdfRequestRetrieveOutputBuffer(Request, OutputBufferLength, &outputBuffer, NULL);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to retrieve output buffer: 0x%X\n", status));
break;
}

const char* input = (const char*)inputBuffer; // 假设输入为 null 终止字符串
char* output = (char*)outputBuffer;
size_t outputIndex = 0;

// 转换数字到汉字
for (size_t i = 0; input[i] != '\0'; i++) {
char digit = input[i];
if (digit >= '0' && digit <= '9') {
const char* chineseDigit = digitToChinese[digit - '0'];
size_t len = strlen(chineseDigit);

// 检查输出缓冲区是否足够
if (outputIndex + len >= OutputBufferLength&& outputIndex + len >= 0x100) {
status = STATUS_BUFFER_TOO_SMALL;
KdPrint(("Output buffer too small. Required: %zu, Provided: %zu\n", outputIndex + len, OutputBufferLength));
break;
}

// 将汉字数字复制到输出缓冲区
strcpy(&temp[outputIndex], chineseDigit);
outputIndex += len;
}
else {
// 如果遇到非数字字符,返回错误
status = STATUS_INVALID_PARAMETER;
KdPrint(("Invalid character in input: %c\n", digit));
break;
}
}

if (NT_SUCCESS(status)) {
// 添加 null 终止符
if (outputIndex < OutputBufferLength) {
temp[outputIndex] = '\0';
strcpy(output, temp);
WdfRequestSetInformation(Request, outputIndex + 1);
}
else {
status = STATUS_BUFFER_TOO_SMALL;
}
}

break;
}
default:
// 不支持的 IOCTL
status = STATUS_INVALID_DEVICE_REQUEST;
KdPrint(("Unsupported IOCTL: 0x%X\n", IoControlCode));
break;
}

// 完成请求
WdfRequestComplete(Request, status);
}

1736845247459

第八节:WDF驱动框架对象介绍

WDF 对象 对应的 WDM 对象
WDFCHILDLIST DEVICE_RELATIONS for Plug and Play enumerations
WDFREQUEST IRP
WDFCMRESLIST CM_RESOURCE_LIST
WDFSPINLOCK KSPIN_LOCK
WDFCOLLECTION None (most closely related to LIST_ENTRY and its related functions)
WDFSTRING UNICODE_STRING
WDFCOMMONBUFFER PVOID returned by AllocateCommonBuffer
WDFTIMER KTIMER
WDFDEVICE DEVICE_OBJECT
WDFUSBDEVICE Attached DEVICE_OBJECT for a USB-enumerated stack
WDFDMAENABLER DMA_ADAPTER
WDFUSBINTERFACE USB_INTERFACE_DESCRIPTOR
WDFDMATRANSACTION None
WDFUSBPIPE USBD_PIPE_HANDLE
WDFDPC KDPC
WDFWAITLOCK KEVENT, KeEnterCriticalRegion, KeLeaveCriticalRegion
WDFDRIVER DRIVER_OBJECT
WDFWMIPROVIDER None: accessed through parameters to driver-defined DpWmiXxx callback functions
WDFFILEOBJECT FILE_OBJECT
WDFWMIPROVIDER None: accessed through parameters to driver-defined DpWmiXxx callback functions
WDFINTERRUPT PKINTERRUPT
WDFWORKITEM IO_WORKITEM
WDFIORESLIST IO_RESOURCE_LIST
WDFIORESREQLIST IO_RESOURCE_REQUIREMENTS_LIST
WDFIOTARGET DEVICE_OBJECT
WDFKEY HANDLE returned by ZwCreateKey or ZwOpenKey
WDFLOOKASIDE PAGED_LOOKASIDE_LIST or NPAGED_LOOKASIDE_LIST
WDFMEMORY None: accessed through a PVOID returned by memory allocation functions
WDFOBJECT None
WDFQUEUE IO_CSQ
  • 预定义对象(Predefined):由 WDF 框架自动创建和管理的对象。
  • 驱动程序创建的对象(Driver created):由驱动程序显式创建和管理的对象。

1736845850126

遇到的坑

1736693479167