楚狂人Windows驱动编程基础教程
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest (irp,IO_NO_INCREMENT); return irp->IoStatus.Status; }
当然,在前面设置分发函数的时候,应该加上:
DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose; DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;
在应用层,打开和关闭这个设备的代码如下:
HANDLE device=CreateFile(\ GENERIC_READ|GENERIC_WRITE,0,0, OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM,0);
if (device == INVALID_HANDLE_VALUE) {
// ?. 打开失败,说明驱动没加载,报错即可 }
// 关闭
CloseHandle(device);
8.3 应用层信息传入
应用层传入信息的时候,可以使用WriteFile,也可以使用DeviceIoControl。DeviceIoControl是双向的,在读取设备的信息也可以使用。因此本书以DeviceIoControl为例子进行说明。DeviceIoControl称为设备控制接口。其特点是可以发送一个带有特定控制码的IRP。同时提供输入和输出缓冲区。应用程序可以定义一个控制码,然后把相应的参数填写在输入缓冲区中。同时可以从输出缓冲区得到返回的更多信息。 当驱动得到一个DeviceIoControl产生的IRP的时候,需要了解的有当前的控制码、输入缓冲区的位置和长度,以及输出缓冲区的位置和长度。其中控制码必须预先用一个宏定义。定义的示例如下:
#define MY_DVC_IN_CODE \\
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, \\ 0xa01, \\
METHOD_BUFFERED, \\
FILE_READ_DATA|FILE_WRITE_DATA)
其中0xa01这个数字是用户可以自定义的。其他的参数请照抄。
楚狂人Windows驱动编程基础教程
下面是获得这三个要素的例子:
NTSTATUS MyDeviceIoControl( PDEVICE_OBJECT dev, PIRP irp) {
// 得到irpsp的目的是为了得到功能号、输入输出缓冲 // 长度等信息。
PIO_STACK_LOCATION irpsp =
IoGetCurrentIrpStackLocation(irp); // 首先要得到功能号
ULONG code = irpsp->Parameters.DeviceIoControl.IoControlCode; // 得到输入输出缓冲长度 ULONG in_len =
irpsp->Parameters.DeviceIoControl.InputBufferLength; ULONG out_len =
irpsp->Parameters.DeviceIoControl.OutputBufferLength; // 请注意输入输出缓冲是公用内存空间的
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
// 如果是符合定义的控制码,处理完后返回成功 if(code == MY_DVC_IN_CODE) {
? 在这里进行需要的处理动作
// 因为不返回信息给应用,所以直接返回成功即可。 // 没有用到输出缓冲
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_SUCCESS; } else {
// 其他的请求不接受。直接返回错误。请注意这里返 // 回错误和前面返回成功的区别。 irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER; }
IoCompleteRequest (irp,IO_NO_INCREMENT); return irp->IoStatus.Status; }
在前面设置分发函数的时候,要加上:
DriverObject->MajorFunctions[IRP_MJ_DEVICE_CONTROL] = MyCreateClose;
楚狂人Windows驱动编程基础教程
应用程序方面,进行DeviceIoControl的代码如下: HANDLE device=CreateFile(\ GENERIC_READ|GENERIC_WRITE,0,0, OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM,0); BOOL ret;
DWORD length = 0; // 返回的长度
if (device == INVALID_HANDLE_VALUE) {
// ? 打开失败,说明驱动没加载,报错即可 }
BOOL ret = DeviceIoControl(device, MY_DVC_IN_CODE, // 功能号
in_buffer, // 输入缓冲,要传递的信息,预先填好 in_buffer_len, // 输入缓冲长度 NULL, // 没有输出缓冲
0, // 输出缓冲的长度为0 &length, // 返回的长度 NULL);
if(!ret) {
// ? DeviceIoControl失败。报错。 }
// 关闭
CloseHandle(device);
8.4 驱动层信息传出
驱动主动通知应用和应用通知驱动的通道是同一个。只是方向反过来。应用程序需要开启一个线程调用DeviceIoControl,(调用ReadFile亦可)。而驱动在没有消息的时候,则阻塞这个IRP的处理。等待有信息的时候返回。 有的读者可能听说过在应用层生成一个事件,然后把事件传递给驱动。驱动有消息要通知应用的时候,则设置这个事件。但是实际上这种方法和上述方法本质相同:应用都必须开启一个线程去等待(等待事件)。而且这样使应用和驱动之间交互变得复杂(需要传递事件句柄)。这毫无必要。
让应用程序简单的调用DeviceIoControl就可以了。当没有消息的时候,这个调用不返回。应用程序自动等待(相当于等待事件)。有消息的时候这个函数返回。并从缓冲区中读到消息。
楚狂人Windows驱动编程基础教程
实际上,驱动内部要实现这个功能,还是要用事件的。只是不用在应用和驱动之间传递事件了。
驱动内部需要制作一个链表。当有消息要通知应用的时候,则把消息放入链表中(请参考前面的“使用LIST_ENTRY”),并设置事件(请参考前面的“使用事件”)。在DeviceIoControl的处理中等待事件。下面是一个例子:这个例子展示的是驱动中处理DeviceIoControl的控制码为MY_DVC_OUT_CODE的部分。实际上驱动如果有消息要通知应用,必须把消息放入队列尾并设置事件g_my_notify_event。MyGetPendingHead获得第一条消息。请读者用以前的知识自己完成其他的部分。
NTSTATUS MyDeviceIoCtrlOut(PIRP irp,ULONG out_len) {
MY_NODE *node; ULONG pack_len; // 获得输出缓冲区。
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
// 从队列中取得第一个。如果为空,则等待直到不为空。 while((node = MyGetPendingHead()) == NULL) {
KeWaitForSingleObject(
&g_my_notify_event,// 一个用来通知有请求的事件 Executive,KernelMode,FALSE,0); }
// 有请求了。此时请求是node。获得PACK要多长。 pack_len = MyGetPackLen(node); if(out_len < pack_len) {
irp->IoStatus.Information = pack_len; // 这里写需要的长度 irp->IoStatus.Status = STATUS_INVALID_BUFFER_SIZE; IoCompleteRequest (irp,IO_NO_INCREMENT); return irp->IoStatus.Status; }
// 长度足够,填写输出缓冲区。 MyWritePackContent(node,buffer); // 头节点被发送出去了,可以删除了 MyPendingHeadRemove (); // 返回成功
irp->IoStatus.Information = pack_len; // 这里写填写的长度 irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest (irp,IO_NO_INCREMENT); return irp->IoStatus.Status; }
相关推荐: