前一次(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; }
看看输出结果:
可以看到原来输出是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编译测试代码。