Windows驱动开发WDM (16)- 完成例程 (重新获得IRP控制权)

前一次(http://blog.csdn.net/zj510/article/details/8350184)学习了完成例程返回STATUS_SUCCESS的情况,也就是驱动调用IoCallDriver后不会再获得IRP。这次来看看再次获得IRP控制权的情况。

 

设置完成例程

先看看设置完成例程那里的代码:

			IoCopyCurrentIrpStackLocationToNext(Irp);

			KEVENT event;
			KeInitializeEvent(&event, NotificationEvent, FALSE);//创建一个event对象。

			//通过第三个参数将event对象传递给完成例程
			IoSetCompletionRoutine(Irp, MyIoCompletion, &event, TRUE, TRUE, TRUE);

			status = IoCallDriver(pdx->NextStackDevice, Irp);

			if (status == STATUS_PENDING)
			{//如果下层驱动是异步完成irp的话,就等待event被触发(完成例程会触发)
			//如果下层驱动是同步完成irp或者其他错误,那么就无需等待了。直接向上返回。
				KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);//等待完成例程触发这个事件,最后一个参数是NULL,表示无限等待

				status = Irp->IoStatus.Status;//获取下层驱动的irp状态
			}

			//获取内核模式下的地址,这个地址一定> 0x7FFFFFFF
			char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
			outBuf[1] = outBuf[2];//将第三个字节复制到第二个字节

			//虽然在底层驱动已经将IRP完成了,但是由于完成例程返回的是
			//STATUS_MORE_PROCESSING_REQUIRED,因此需要再次调用IoCompleteRequest!
			IoCompleteRequest (Irp, IO_NO_INCREMENT);

跟前一个版本相比,有3个分别:

1. 创建一个事件对象,初始化,并且在IoCallDriver后面等待这个事件;

2. 事件触发后,将输出缓冲的第三个字节赋值给第二个字节,也就是说过滤驱动再更改一下返回的内容;

3. 调用IoCompleteRequest再次完成IRP(因为完成例程返回了STATUS_MORE_PROCESSING_REQUIRED)

IoSetCompletionRoutine的第三个参数对应完成例程的第三个参数。也就是说我们可以传一些数据给完成例程。

VOID IoSetCompletionRoutine(
  _In_      PIRP Irp,
  _In_opt_  PIO_COMPLETION_ROUTINE CompletionRoutine,
  _In_opt_  PVOID Context,
  _In_      BOOLEAN InvokeOnSuccess,
  _In_      BOOLEAN InvokeOnError,
  _In_      BOOLEAN InvokeOnCancel
);

 

完成例程代码

NTSTATUS MyIoCompletion(IN PDEVICE_OBJECT fdo, IN PIRP irp, IN PVOID context)
{
	KdPrint(("Enter MyIoCompletion\n"));

	//判断是否挂起返回,就是说下层驱动是挂起irp,然后再处理irp(异步irp)
	if(irp->PendingReturned)
	{//设置完成例程那里,只有下层驱动返回pending状态才等待事件,那么我们
		//这里只需要挂起返回的时候才设置事件信号
		KeSetEvent((PKEVENT)context, IO_NO_INCREMENT, FALSE);
		
	}

	KdPrint(("Leave MyIoCompletion\n"));

	//返回STATUS_MORE_PROCESSING_REQUIRED,当前驱动将再次获得irp的控制权,
	//同时也需要再次调用IoCompleteRequest来完成irp
	return STATUS_MORE_PROCESSING_REQUIRED;
}

跟前面的版本比较:

1. 当挂起返回的时候,给事件设置信号,这个事件对象是参数传进来的。

2. 返回STATUS_MORE_PROCESSING_REQUIRED

因为完成例程里面设置了信号,那么KeWaitForSingleObject就会返回。

 

测试代码:

跟前一个文章一样,相当的简单,就是起2个线程,发2个IRP请求:

// TestWDMDriver.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <process.h>

#define DEVICE_NAME L"\\\\.\\HelloWDM"
#define DEVICE_NAME2 L"\\\\.\\HelloWDM2"
#define DEVICE_EX L"\\\\.\\HelloWDM_EX"

void Test(void* pParam)
{
	int index = (int)pParam;
	//设置overlapped标志,表示异步打开
	HANDLE hDevice;
	
	hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);

	wprintf(L"CreateFile, name: %s, ret: %x\n", DEVICE_NAME, hDevice);
	
	

	if (hDevice != INVALID_HANDLE_VALUE)
	{
		char inbuf[100] = {0};
		
		sprintf(inbuf, "hello world %d", index);
		char outbuf[100] = {0};
		DWORD dwBytes = 0;

		DWORD dwStart = GetTickCount();

		printf("input buffer: %s\n", inbuf);
		
		OVERLAPPED ol = {0};
		ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

		BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS), 
			inbuf, strlen(inbuf), outbuf, 100, &dwBytes, &ol);

		printf("DeviceIoControl thread %d, returns %d, last error: %d, used: %d ms, input: %s\n", index, b, GetLastError(), GetTickCount() - dwStart, inbuf);


		WaitForSingleObject(ol.hEvent, INFINITE);

		DWORD dwEnd = GetTickCount();
		//将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。
		for (int i = 0; i < strlen(inbuf); i++)
		{
			outbuf[i] = outbuf[i] ^ 'm';
		}

		printf("Verify thread %d, outbuf: %s, used: %d ms\n", index, outbuf, dwEnd - dwStart);

		CloseHandle(hDevice);

	}
	else
		printf("CreateFile failed, err: %x\n", GetLastError());
}


int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE t1 = (HANDLE)_beginthread(Test, 0, (void*)0);
	_beginthread(Test, 0, (void*)1);

	WaitForSingleObject(t1, INFINITE);

	printf("Test ends\n");
	return 0;
}


看看输出结果:

Windows驱动开发WDM (16)- 完成例程 (重新获得IRP控制权)_第1张图片

可以看到原来输出是xello world 0和xello world 1,现在第二个字节变成l了。这是因为过滤驱动在下层驱动完成后,又再次更改了输出内容,就是将第三个字节赋值给第二个字节了。

 

代码:http://download.csdn.net/detail/zj510/4913259

目录1: 功能驱动,从控制面板里面安装

目录2: 过滤驱动,直接右键inf文件安装,同时再改一个注册表,参考http://blog.csdn.net/zj510/article/details/8332658

测试代码:一段简单的测试代码,起2个线程,发2个IRP请求。

WDK7600编译驱动,VS2008编译测试代码。

你可能感兴趣的:(Windows驱动开发WDM (16)- 完成例程 (重新获得IRP控制权))