利用结构体实现一个控制码多个功能的驱动通信方式

SharedProtocol.h(通信协议头)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once

#define MAX_PAYLOAD 512

// IOCTL 定义
#define IOCTL_EXCHANGE_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

// 消息类型
typedef enum _MESSAGE_TYPE {
MSG_TYPE_SEND = 1,
MSG_TYPE_QUERY = 2
} MESSAGE_TYPE;

// 用户层传入结构体
typedef struct _USER_REQUEST {
MESSAGE_TYPE MsgType;
ULONG PayloadSize;
CHAR Payload[MAX_PAYLOAD];
} USER_REQUEST, *PUSER_REQUEST;

Common.h(通用头)

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include <ntddk.h>
#include "SharedProtocol.h"

#define DEVICE_NAME L"\\Device\\MyMinimalDevice"
#define SYMLINK_NAME L"\\DosDevices\\MyMinimalDevice"

// 日志输出宏
#define Log(...) DbgPrint("qi: " __VA_ARGS__)

// 全局缓冲
extern CHAR g_KernelMessage[MAX_PAYLOAD];
extern PDEVICE_OBJECT g_DeviceObj;

Driver.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
#include "Common.h"

NTSTATUS CreateDevAndSymLink(PDRIVER_OBJECT);
NTSTATUS DispatchAll(PDEVICE_OBJECT, PIRP);
NTSTATUS DispatchIoctl(PDEVICE_OBJECT, PIRP);

CHAR g_KernelMessage[MAX_PAYLOAD] = "默认内核消息";
PDEVICE_OBJECT g_DeviceObj = NULL;

VOID UnloadDriver(PDRIVER_OBJECT drv)
{
UNICODE_STRING symlink = RTL_CONSTANT_STRING(SYMLINK_NAME);
IoDeleteSymbolicLink(&symlink);
if (g_DeviceObj) IoDeleteDevice(g_DeviceObj);
Log("卸载驱动完成\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT drv, PUNICODE_STRING reg)
{
UNREFERENCED_PARAMETER(reg);

NTSTATUS status = CreateDevAndSymLink(drv);
if (!NT_SUCCESS(status)) return status;

for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
drv->MajorFunction[i] = DispatchAll;

drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
drv->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

Device.c(通用 IRP 分发和设备创建)

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
#include "Common.h"

NTSTATUS CreateDevAndSymLink(PDRIVER_OBJECT drv)
{
UNICODE_STRING devName = RTL_CONSTANT_STRING(DEVICE_NAME);
UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYMLINK_NAME);
NTSTATUS status = IoCreateDevice(drv, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_DeviceObj);
if (!NT_SUCCESS(status)) return status;

g_DeviceObj->Flags |= DO_BUFFERED_IO;
return IoCreateSymbolicLink(&symLink, &devName);
}

NTSTATUS DispatchAll(PDEVICE_OBJECT dev, PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG info = 0;

switch (stack->MajorFunction)
{
case IRP_MJ_CREATE: Log("Create\n"); break;
case IRP_MJ_CLOSE: Log("Close\n"); break;
case IRP_MJ_READ:
{
CHAR* userBuf = irp->AssociatedIrp.SystemBuffer;
ULONG len = stack->Parameters.Read.Length;
size_t toCopy = min(len, strlen(g_KernelMessage) + 1);
RtlCopyMemory(userBuf, g_KernelMessage, toCopy);
info = (ULONG)toCopy;
break;
}
case IRP_MJ_WRITE:
{
CHAR* inBuf = irp->AssociatedIrp.SystemBuffer;
ULONG len = stack->Parameters.Write.Length;
RtlZeroMemory(g_KernelMessage, MAX_PAYLOAD);
RtlCopyMemory(g_KernelMessage, inBuf, min(len, MAX_PAYLOAD - 1));
Log("Write: %s\n", g_KernelMessage);
info = len;
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}

irp->IoStatus.Status = status;
irp->IoStatus.Information = info;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}

Ioctl.c(专门处理 IRP_MJ_DEVICE_CONTROL)

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
#include "Common.h"

NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PUSER_REQUEST req = (PUSER_REQUEST)irp->AssociatedIrp.SystemBuffer;
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;

NTSTATUS status = STATUS_SUCCESS;
ULONG info = 0;

switch (stack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_EXCHANGE_MESSAGE:
if (inLen < sizeof(USER_REQUEST)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}

switch (req->MsgType)
{
case MSG_TYPE_SEND:
RtlZeroMemory(g_KernelMessage, MAX_PAYLOAD);
RtlCopyMemory(g_KernelMessage, req->Payload, min(req->PayloadSize, MAX_PAYLOAD - 1));
Log("收到结构体消息: %s\n", g_KernelMessage);
break;

case MSG_TYPE_QUERY:
if (outLen >= sizeof(USER_REQUEST)) {
PUSER_REQUEST out = (PUSER_REQUEST)irp->AssociatedIrp.SystemBuffer;
out->MsgType = MSG_TYPE_QUERY;
out->PayloadSize = (ULONG)min(strlen(g_KernelMessage), MAX_PAYLOAD - 1);
RtlCopyMemory(out->Payload, g_KernelMessage, out->PayloadSize + 1);
info = sizeof(USER_REQUEST);
} else {
status = STATUS_BUFFER_TOO_SMALL;
}
break;

default:
status = STATUS_INVALID_PARAMETER;
break;
}
break;

default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}

irp->IoStatus.Status = status;
irp->IoStatus.Information = info;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}

✅ 优点总结:

  • 结构清晰:每类功能单独文件管理。
  • 扩展方便:想加 IOCTL 只需改 Ioctl.c,结构体放 SharedProtocol.h
  • 日志可控:统一 Log() 宏打印,方便控制开关。
  • 更符合内核代码风格,适合以后上 WDF 或移植到 KMDF。

✅ 用户层测试代码 (UserApp.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
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"
#define IOCTL_EXCHANGE_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define MAX_PAYLOAD 512

// 消息类型
typedef enum _MESSAGE_TYPE {
MSG_TYPE_SEND = 1,
MSG_TYPE_QUERY = 2
} MESSAGE_TYPE;

// 用户请求结构体
typedef struct _USER_REQUEST {
MESSAGE_TYPE MsgType;
ULONG PayloadSize;
CHAR Payload[MAX_PAYLOAD];
} USER_REQUEST, *PUSER_REQUEST;

void TestIoctl(HANDLE hDevice) {
USER_REQUEST req;
DWORD bytesReturned = 0;
memset(&req, 0, sizeof(req));

// 发送消息
req.MsgType = MSG_TYPE_SEND;
strcpy_s(req.Payload, MAX_PAYLOAD, "Hello from user space!");
req.PayloadSize = (ULONG)strlen(req.Payload);

if (!DeviceIoControl(
hDevice, // 设备句柄
IOCTL_EXCHANGE_MESSAGE, // IOCTL 控制码
&req, // 输入缓冲区
sizeof(req), // 输入缓冲区大小
NULL, // 输出缓冲区
0, // 输出缓冲区大小
&bytesReturned, // 返回的字节数
NULL // 重叠结构
)) {
printf("DeviceIoControl failed with error %lu\n", GetLastError());
} else {
printf("Message sent successfully: %s\n", req.Payload);
}

// 查询消息
req.MsgType = MSG_TYPE_QUERY;
req.PayloadSize = 0; // 不需要传输有效负载,只是查询

if (!DeviceIoControl(
hDevice, // 设备句柄
IOCTL_EXCHANGE_MESSAGE, // IOCTL 控制码
&req, // 输入缓冲区
sizeof(req), // 输入缓冲区大小
&req, // 输出缓冲区
sizeof(req), // 输出缓冲区大小
&bytesReturned, // 返回的字节数
NULL // 重叠结构
)) {
printf("DeviceIoControl failed with error %lu\n", GetLastError());
} else {
printf("Message received: %s\n", req.Payload);
}

}

int main() {
// 打开驱动设备
HANDLE hDevice = CreateFile(
DEVICE_NAME, // 驱动设备名称
GENERIC_READ | GENERIC_WRITE, // 访问权限
0, // 共享模式
NULL, // 安全属性
OPEN_EXISTING, // 打开现有设备
0, // 文件属性
NULL // 模板文件句柄
);

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

// 测试 IOCTL
TestIoctl(hDevice);

// 关闭设备句柄
CloseHandle(hDevice);
return 0;

}

代码说明:

CreateFile:用于打开设备,成功时返回设备句柄。如果打开失败,会返回 INVALID_HANDLE_VALUE,并打印错误信息。

DeviceIoControl:用于与驱动进行 IO 控制交换。

第一个 DeviceIoControl 调用发送消息到内核驱动,控制码为 IOCTL_EXCHANGE_MESSAGE。

第二个 DeviceIoControl 调用查询内核驱动中的消息。

USER_REQUEST 结构体:定义了发送和查询消息的格式,包括 MsgType(消息类型)和 Payload(消息内容)。

错误处理:检查 DeviceIoControl 调用的返回值,失败时通过 GetLastError() 获取详细的错误信息。