Windows驱动开发入门-应用与内核通信

内核部分

生成CDO

IoCreateDevice

内核想要与应用通信时,必须生成控制设备对象CDO。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
//从DriverEntry参数获得
IN ULONG DeviceExtensionSize,
//设备扩展大小 后面讲
IN OPTIONAL PUNICODE_STRING DeviceName,
//设备名 可空但别
IN DEVICE_TYPE DeviceType,
//设备类型
IN ULONG DeviceCharacteristics,
//设备属性
IN BOOLEAN Exclusive,
//是否为独占设备
OUT PDEVICE_OBJECT* DeviceObject
//返回生成设备对象指针
)

IoCreateDeviceSecure

上面那个有默认安全属性,必须得管理员权限应用才能打开,所以改用这个不安全的来玩。设备GUID理论上要用CoCreateGuid来生成,但有别的方法比如在线工具:https://www.gjk.cn/guid。

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS IoCreateDeviceSecure(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN OPTIONAL PUNICODE_STRING DeviceName,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
IN PCUNICODE_STRING DefaultSDDLString,
//表示安全设置的字符串 待会儿例子有个万能(但最不安全)的一个
IN LPCGUID DeviceClassGuid,
//设备GUID 该驱动一直用这个GUID不变
OUT PDEVICE_OBJECT* DeviceObject
)

IoDeleteDevice

删除控制设备。

符号链接

设备对象DO可以没名字,但控制设备对象CDO需要个名字,这样才可以被暴露出来。暴露给应用层需要建立符号链接。最稳妥的方法还是用GUID比较好,符号链接很容易冲突。当然有一种粗暴的方法就是删除已存在的符号链接再生成一个自己的。

1
2
3
4
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
)

删除符号链接。

分发函数

处理发送给设备对象请求的函数,入口在DriverEntry中对PDRIVER_OBJECT的MajorFunction中设置。这个MajorFunction记录多个不同请求的处理函数,但我们把它全都设置成该分发函数,然后在分发函数中处理各种请求。标准的分发函数原型:

1
2
3
4
NTSTATUS DriverDispatcher(
IN PDEVICE_OBJECT dev,
IN PIRP irp
)

请求的处理

在分发函数中,需要用IoGetCurrentIrpStackLocation先获取请求的当前栈空间,然后判断请求是不是发给自己这个驱动的,是的话对请求种类判断,不是的话返回错误即可。最后设置返回输出需要的空间和请求完成状态,最后用IoCompleteRequest结束请求。

例子

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
#include <ntddk.h>
PDEVICE_OBJECT g_cdo = NULL;
CONST GUID CWK_GUID_CLASS_MYCDO={0x17a0d1e0L,0x3249,0x12e1,{0x92,0x16,0x45,0x1a,0x21,0x30,0x29,0x06}};
#define CWK_CDO_SYB_NAME L"\\??\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
VOID DriverUnload(PDRIVER_OBJECT pDriver) {
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
ASSERT(g_cdo != NULL);
IoDeleteSymbolicLink(&cdo_syb);
IoDeleteDevice(g_cdo);
return;
};
NTSTATUS DriverDispatcher(IN PDEVICE_OBJECT dev, IN PIRP irp) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
while (dev == g_cdo) {
// 如果这个请求不是发给g_cdo的,那就非常奇怪了。因为这个驱动只生成过这一个设备。所以可以直接返回失败。
if (irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE)
// 生成和关闭请求,这个一律简单地返回成功就可以了。就是无论何时打开和关闭都可以成功。
break;
if (irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
// 处理DeviceIoControl。
PVOID buffer = irp->AssociatedIrp.SystemBuffer; //输入输出是同一个缓冲区
ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG len;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode) {
case CWK_DVC_SEND_STR: {
ASSERT(buffer != NULL);
ASSERT(inlen > 0);
ASSERT(outlen == 0);
DbgPrint((char*)buffer);
// 已经打印过了,那么现在就可以认为这个请求已经成功。
break;
};
case CWK_DVC_RECV_STR:
default: {
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
status = STATUS_INVALID_PARAMETER;
break;
};
};
};
break;
};
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
irp->IoStatus.Information = ret_len;
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
};
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path) {
NTSTATUS status=STATUS_SUCCESS;
ULONG i=0;
UCHAR mem[256] = { 0 };
// 生成一个控制设备。然后生成符号链接。
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L"\\Device\\cwk_3948d33e");
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
KdBreakPoint();
// 生成一个控制设备对象。
status = IoCreateDeviceSecure(pDriver, 0, &cdo_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl, (LPCGUID)&CWK_GUID_CLASS_MYCDO, &g_cdo);
if (!NT_SUCCESS(status))
return status;
// 生成符号链接.
IoDeleteSymbolicLink(&cdo_syb);
status = IoCreateSymbolicLink(&cdo_syb, &cdo_name);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(g_cdo);
return status;
};
// 所有的分发函数都设置成一样的。
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
pDriver->MajorFunction[i] = DriverDispatcher;
// 支持动态卸载。
pDriver->DriverUnload = DriverUnload;
// 清除控制设备的初始化标记。
g_cdo->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
};

应用部分

应用层一般直接用CreateFile打开设备,然后用DeviceIoControl发送请求,发送的请求宏为CTL_CODE,像上面内核部分例子一样使用即可,该宏的使用方法为:

1
2
3
4
5
6
CTL_CODE(
FILE_DEVICE_UNKNOWN, //设备类型 因为这驱动与任何硬件没关系 所以未知就行
0x911, //功能号 用户只能使用0x7FF~0xFFF
METHOD_BUFFERED, //使用缓冲方式
FILE_WRITE_DATA //操作所需权限
)

不讲了直接上例子:

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
#include <Windows.h>
#include <tchar.h>
#include <cstdio>
#define CWK_DEV_SYM L"\\\\.\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
int _tmain(int argc, _TCHAR* argv[]) {
HANDLE device = NULL;
ULONG ret_len = 0;
int ret = 0;
char msg[] = "Hello driver, this is a message from app.\r\n";
// 打开设备.每次要操作驱动的时候,先以此为例子打开设备
device = ::CreateFile(CWK_DEV_SYM, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (device == INVALID_HANDLE_VALUE) {
printf("coworker demo: Open device failed.\r\n");
return -1;
}
else
printf("coworker demo: Open device successfully.\r\n");
if (!::DeviceIoControl(device, CWK_DVC_SEND_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0)) {
printf("coworker demo: Send message failed.\r\n");
ret = -2;
}
else
printf("coworker demo: Send message successfully.\r\n");
::CloseHandle(device);
return ret;
};

阻塞、等待、安全设计

在这个例子中,限定字符串最大长度为$511$字节,再加一个结束符。输入缓冲区超长的一律返回失败,输出缓冲区最小长度为$512$字节,小于这个长度的一律返回失败即使实际字符串小于这个长度。缓冲区用LIST_ENTRY实现,每次应用层来轮循时卸下一个链表,每个节点为$512$字节,还要用自旋锁来通知应用程序缓冲区有内容可取。

函数KdBreakPoint和宏ASSERT千万不要在发行版中使用,否则直接唤起蓝屏。函数strlen也不要使用,为了寻找结束符而没有限度,搜索到无效空间后唤起蓝屏,应使用strnlen,同理使用strncpy

输出的话如果还使用等待事件则特别繁琐,这里直接用死循环轮循,但为了防止处理器占用率过高,搞个进程休眠使。

内核部分例子:

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
#include <ntddk.h>
PDEVICE_OBJECT g_cdo = NULL;
CONST GUID CWK_GUID_CLASS_MYCDO={0x17a0d1e0L,0x3249,0x12e1,{0x92,0x16,0x45,0x1a,0x21,0x30,0x29,0x06}};
#define CWK_CDO_SYB_NAME L"\\??\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
// 定义一个链表用来保存字符串
#define CWK_STR_LEN_MAX 512
typedef struct {
LIST_ENTRY list_entry;
char buf[CWK_STR_LEN_MAX];
} CWK_STR_NODE,*PCWK_STR_NODE;
// 还必须有一把自旋锁来保证链表操作的安全性
KSPIN_LOCK g_cwk_lock = {0};
// 一个事件来标识是否有字符串可以取
KEVENT g_cwk_event = {0};
// 必须有个链表头
LIST_ENTRY g_cwk_str_list = {0};
#define MEM_TAG 'cwkr'
// 分配内存并初始化一个链表节点
PCWK_STR_NODE cwkMallocStrNode(VOID) {
PCWK_STR_NODE ret = ExAllocatePoolWithTag(NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG);
if (ret == NULL)
return NULL;
return ret;
};
VOID cwkUnload(PDRIVER_OBJECT pDriver) {
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
PCWK_STR_NODE str_node = {0};
ASSERT(g_cdo != NULL);
IoDeleteSymbolicLink(&cdo_syb);
IoDeleteDevice(g_cdo);
// 负责的编程态度:释放分配过的所有内核内存。
while (TRUE) {
str_node = (PCWK_STR_NODE)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock);
// str_node = RemoveHeadList(&g_cwk_str_list);
if (str_node != NULL)
ExFreePool(str_node);
else
break;
};
return;
};
NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
while (dev == g_cdo) {
// 如果这个请求不是发给g_cdo的,那就非常奇怪了。因为这个驱动只生成过这一个设备。所以可以直接返回失败。
if (irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE)
// 生成和关闭请求,这个一律简单地返回成功就可以了。就是无论何时打开和关闭都可以成功。
break;
if (irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
// 处理DeviceIoControl。
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG len=0;
PCWK_STR_NODE str_node = NULL;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode) {
case CWK_DVC_SEND_STR: {
ASSERT(buffer != NULL);
ASSERT(outlen == 0);
// 安全的编程态度之一:检查输入缓冲的长度对于长度超出预期的,果断返回错误。
if (inlen > CWK_STR_LEN_MAX) {
status = STATUS_INVALID_PARAMETER;
break;
};
// 安全的编程态度之二:检查字符串的长度,不要使用strlen!如果使用strlen,一旦攻击者故意输入没有结束符的字符串,会导致内核驱动访问非法内存空间而崩溃。
DbgPrint("strnlen = %d\r\n", strnlen((char*)buffer, inlen));
if (strnlen((char*)buffer, inlen) == inlen) {
// 字符串占满了缓冲区,且中间没有结束符。立刻返回错误。
status = STATUS_INVALID_PARAMETER;
break;
};
// 现在可以认为输入缓冲是安全而且不含恶意的。分配节点。
str_node = cwkMallocStrNode();
if (str_node == NULL) {
// 如果分配不到空间了,返回资源不足的错误
status = STATUS_INSUFFICIENT_RESOURCES;
break;
};
// 前面已经检查了缓冲区中的字符串的确长度合适而且含有结束符,所以这里用什么函数来拷贝字符串对安全性而言并不非常重要。
strncpy(str_node->buf, (char*)buffer, CWK_STR_LEN_MAX);
// 插入到链表末尾。用锁来保证安全性。
ExInterlockedInsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node, &g_cwk_lock);
// InsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node);
// 打印
// DbgPrint((char *)buffer);
// 那么现在就可以认为这个请求已经成功。因为刚刚已经插入了一个,那么可以设置事件来表明队列中已经有元素了。
KeSetEvent(&g_cwk_event, 0, TRUE);
break;
};
case CWK_DVC_RECV_STR: {
ASSERT(buffer != NULL);
ASSERT(inlen == 0);
// 应用要求接收字符串。对此,安全上要求是输出缓冲要足够长。
if (outlen < CWK_STR_LEN_MAX) {
status = STATUS_INVALID_PARAMETER;
break;
};
while (TRUE) {
// 插入到链表末尾。用锁来保证安全性。
str_node = (PCWK_STR_NODE)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock);
// str_node = RemoveHeadList(&g_cwk_str_list);
if (str_node != NULL) {
// 这种情况下,取得了字符串。那就拷贝到输出缓冲中。然后整个请求就返回了成功。
strncpy((char*)buffer, str_node->buf, CWK_STR_LEN_MAX);
ret_len = strnlen(str_node->buf, CWK_STR_LEN_MAX) + 1;
ExFreePool(str_node);
break;
}
else
// 对于合法的要求,在缓冲链表为空的情况下,等待事件进行阻塞。也就是说,如果缓冲区中没有字符串,就停下来等待 。这样应用程序也会被阻塞住,DeviceIoControl是不会返回的。但是一旦有就会返回。等于驱动“主动”通知了应用。
KeWaitForSingleObject(&g_cwk_event, Executive, KernelMode, 0, 0);
};
break;
};
default: {
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
status = STATUS_INVALID_PARAMETER;
break;
};
};
};
break;
};
// 返回结果
irp->IoStatus.Information = ret_len;
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
};
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path) {
NTSTATUS status=STATUS_SUCCESS;
ULONG i=0;
UCHAR mem[256] = { 0 };
// 生成一个控制设备。然后生成符号链接。
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L"\\Device\\cwk_3948d33e");
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
KdBreakPoint();
// 生成一个控制设备对象。
status = IoCreateDeviceSecure(pDriver, 0, &cdo_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl, (LPCGUID)&CWK_GUID_CLASS_MYCDO, &g_cdo);
if (!NT_SUCCESS(status))
return status;
// 生成符号链接.
IoDeleteSymbolicLink(&cdo_syb);
status = IoCreateSymbolicLink(&cdo_syb, &cdo_name);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(g_cdo);
return status;
};
// 初始化事件、锁、链表头。
KeInitializeEvent(&g_cwk_event, SynchronizationEvent, TRUE);
KeInitializeSpinLock(&g_cwk_lock);
InitializeListHead(&g_cwk_str_list);
// 所有的分发函数都设置成一样的。
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
pDriver->MajorFunction[i] = cwkDispatch;
// 支持动态卸载。
pDriver->DriverUnload = cwkUnload;
// 清除控制设备的初始化标记。
g_cdo->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
};

用户部分例子:

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
#include <Windows.h>
#include <tchar.h>
#include <cstdio>
#define CWK_DEV_SYM L"\\\\.\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
int _tmain(int argc, _TCHAR* argv[]) {
HANDLE device = NULL;
ULONG ret_len;
int ret = 0;
char msg[] = "Hello driver, this is a message from app.\r\n";
char tst_msg[1024] = { 0 };
// 打开设备.每次要操作驱动的时候,先以此为例子打开设备
device = CreateFile(CWK_DEV_SYM, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (device == INVALID_HANDLE_VALUE){
printf("coworker demo: Open device failed.\r\n");
return -1;
}
else
printf("coworker demo: Open device successfully.\r\n");
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0)){
printf("coworker demo: Send message failed.\r\n");
ret = -2;
}
else
printf("coworker demo: Send message successfully.\r\n");
// 这里开始,其实是对驱动的一系列测试。分配3个字符串:
// 1.长度为0.应该可以正常输入。
// 2.长度为511字节,应该可以正常输入。
// 3.长度为512字节,应该返回失败。
// 4.长度为1024字节的字符串,但声明缓冲区长度为128,应该返回失败。
// 5.第一次读取,应该读出msg的内容。
// 5.第一次读取,应该读出长度为511字节的字符串。
// 6.第二次读取,应该读出长度为0的字符串。
do {
memset(tst_msg, '\0', 1);
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 1, NULL, 0, &ret_len, 0)){
ret = -3;
break;
}
else
printf("TEST1 PASS.\r\n");
memset(tst_msg, '\0', 512);
memset(tst_msg, 'a', 511);
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 512, NULL, 0, &ret_len, 0)){
ret = -5;
break;
}
else
printf("TEST2 PASS.\r\n");
memset(tst_msg, '\0', 513);
memset(tst_msg, 'a', 512);
if (DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 513, NULL, 0, &ret_len, 0)){
// 这个缓冲区已经过长,理应返回失败。如果成功了则认为是错误。
ret = -5;
break;
}
else
printf("TEST3 PASS.\r\n");
memset(tst_msg, '\0', 1024);
memset(tst_msg, 'a', 1023);
if (DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 128, NULL, 0, &ret_len, 0)){
// 这个缓冲区虽然不过长,但是字符串过长,理应返回失败。如果成功了则认为是错误。
ret = -5;
break;
}
else
printf("TEST4 PASS.\r\n");
free(tst_msg);
// 现在开始测试输出。第一个读出的应该是msg.
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) == 0 || ret_len != strlen(msg) + 1){
ret = -6;
break;
}
else
printf("TEST5 PASS.\r\n");
// 第二个读出的应该是长度为0的空字符串。
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) == 0 || ret_len != 1){
ret = -6;
break;
}
else
printf("TEST6 PASS.\r\n");
// 第三个读出的应该是长度为511的全a字符串
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) != 0 || ret_len != 511 + 1){
ret = -6;
break;
}
else
printf("TEST7 PASS.\r\n");
} while (0);
CloseHandle(device);
return ret;
};