WDF驱动开发(3)- 用户模式程序和驱动的数据交互

当用户模式的程序调用驱动的时候,怎么传递数据呢?

通常用户模式程序和驱动的数据交互有3种办法:buffer方式,direct方式和其他方式。可以看之前我写的WDM驱动模型:http://blog.csdn.net/zj510/article/details/8212032

那么在WDF里面又如何操作呢?我们考虑给WDF驱动增加一个功能:将WRITE请求的数据写入设备对象上下文,然后当READ请求过来的时候,将设备对象上下文里面的数据读出来并且返回给用户模式程序。

WdfRequestRetrieveInputBuffer和WdfRequestRetrieveOutputBuffer函数

WDF提供了2个函数,这2个函数可以获得请求的输入输出缓冲。READ请求只有OutputBuffer,WRITE请求只有InputBuffer. IO control请求同时拥有输入输出缓冲。

比如

char* in_buf = NULL;
WdfRequestRetrieveInputBuffer(Request, 10, &in_buf, NULL);

通过上面的代码,可以获得Request的输入请求,in_buf是一个指针,如果函数调用成功的话,那么in_buf将指向一个输入缓冲。in_buf指向一个内核模式下的地址。我们可以直接使用这个指针,比如读取或者写入数据。

如果是READ请求的话,上面的函数会失败,因为READ请求是没有输入缓冲的。如果是WRITE请求的话,那么就可以成功获取输入缓冲。

 

实现一个写入读取的功能

这里给驱动增加一个简单的功能,将WRITE请求的数据写入设备上下文,然后当有READ请求过来的时候,将设备上下文里面的数据读出来,并且存入READ请求的输出缓冲。代码如下:

static VOID EvtIoPDPControlDevice( WDFQUEUE Queue , WDFREQUEST Request )
{
	
	WDF_REQUEST_PARAMETERS Params;  
	WDFREQUEST req;
	NTSTATUS status;
	WDFDEVICE  hDevice;
	DEVICE_CONTEXT* dev_ctx = NULL;

	//buffer
	char* in_buf = NULL;
	char* out_buf = NULL;

	//获取请求的参数
	WDF_REQUEST_PARAMETERS_INIT(&Params);  
	WdfRequestGetParameters(Request,&Params);

	KdPrint(("EvtIoPDPControlDevice, type: %x\n", Params.Type));

	
	status = WdfRequestRetrieveInputBuffer(Request, 10, &in_buf, NULL);
	
	KdPrint(("Get input buffer, ret: %x, buffer: %x", status, in_buf));

	
	status = WdfRequestRetrieveOutputBuffer(Request, 10, &out_buf, NULL);
	
	KdPrint(("Get output buffer, ret: %x, buffer: %x", status, out_buf));
		
	//获得Queue关联的设备对象
	hDevice = WdfIoQueueGetDevice(Queue);

	//获得设备对象的上下文
	dev_ctx = WdfObjectGetTypedContext(hDevice, DEVICE_CONTEXT);

	KdPrint(("Get the context of current device, %x", dev_ctx));

	
	switch(Params.Type)
	{
		case WdfRequestTypeRead:
			{
				if(out_buf != NULL)
				{
					out_buf[0] = '1';

					KdPrint(("write the data in context buffer into READ request\n"));

					//将设备对象上下文里面的数据返回给用户模式程序
					RtlCopyMemory(out_buf, dev_ctx->_Buffer, 10);

					WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, 10);
				}
			}		
			break;
		case WdfRequestTypeWrite:
			{
				if(in_buf != NULL)
				{
					KdPrint(("input buffer, first element: %c\n", in_buf[0]));

					//将WRITE请求的数据写入设备对象上下文的缓冲区
					RtlCopyMemory(dev_ctx->_Buffer, in_buf, 10);

					WdfRequestComplete(Request, STATUS_SUCCESS);
				}
			}
			break;
		default:
			WdfRequestComplete(Request, STATUS_SUCCESS);
			break;
	}
		

}


测试代码

写几行测试代码,就是简单的写入,然后读取:

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

#include "stdafx.h"
#include <Windows.h>

#define MYDEVICE L"\\\\.\\MyWDF_LINK"

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hDevice = CreateFile(MYDEVICE,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

	if (hDevice == INVALID_HANDLE_VALUE)
	{
		wprintf(L"Failed to open device %s, err: %x\n", MYDEVICE, GetLastError());
		
	}
	else
	{
		wprintf(L"Open device %s successfully\n", MYDEVICE);

		byte buf[10] = {0};
		DWORD dwRet = 0;

		memcpy(buf, "abcdefghi", 9);

		BOOL b = WriteFile(hDevice, buf, 10, &dwRet, NULL);
		wprintf(L"call WriteFile, ret: %d\n", b);

		char out_buf[10] = {0};
		dwRet = 0;
		b = ReadFile(hDevice, out_buf, 10, &dwRet, NULL);
		printf("call ReadFile, ret: %d, content: %s, read: %d\n", b, out_buf, dwRet);

		

		CloseHandle(hDevice);

		wprintf(L"Closed handle\n");
	}

	return 0;
}


ok,运行一下:

WDF驱动开发(3)- 用户模式程序和驱动的数据交互_第1张图片

 

注意:怎么设置驱动是以哪种方式处理内存交互的呢?看这个,http://msdn.microsoft.com/en-us/library/windows/hardware/dn265604(v=vs.85).aspx

贴2段比较重要的

KMDF A KMDF driver calls WdfDeviceInitSetIoTypeEx to set a buffer-access method for read and write requests. For device I/O control requests, the framework uses the buffer type that is encoded in the I/O control code (IOCTL).
If the driver does not call WdfDeviceInitSetIoTypeEx, the framework sets the driver's buffer-access method to WdfDeviceIoBuffered, for the specified device.

第一段是说用WdfDeviceInitSetIoTypeEx里设置READ/WRITE的工作方式,如果是IO control request,那么需要使用另外一种办法来设置。

第二段是说如果驱动没有调用WdfDeviceInitSetIoTypeEx,则WDF框架会默认使用Buffered方式。
本例中,没有调用WdfDeviceInitSetIoTypeEx,那就是说使用默认的buffered方式,也就是框架会在内核空间开辟一个缓冲,然后将用户模式的数据统统拷贝到开辟出来的内核模式的缓冲里面,这个过程无需程序员控制,系统自动会处理。

 

完整的驱动代码:

#include <fltKernel.h>
#include <wdf.h>
#include <wdfdriver.h>
#include <wdfrequest.h>

#define MYWDF_KDEVICE L"\\Device\\MyWDF_Device"//设备名称,其他内核模式下的驱动可以使用
#define MYWDF_LINKNAME L"\\DosDevices\\MyWDF_LINK"//符号连接,这样用户模式下的程序可以使用这个驱动设备。

typedef struct
{
	WDFQUEUE _DefaultQueue;

	char _Buffer[100];
}DEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT);


//声明回调
EVT_WDF_DRIVER_UNLOAD EvtDriverUnload;
EVT_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtFileClose;

EVT_WDF_IO_QUEUE_IO_DEFAULT EvtIoPDPControlDevice;



NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject , IN PUNICODE_STRING RegistryPath )
{  
	NTSTATUS status;	
	WDF_OBJECT_ATTRIBUTES object_attribs;

	//驱动对象相关
	WDF_DRIVER_CONFIG cfg;//驱动的配置
	WDFDRIVER drv = NULL;//wdf framework 驱动对象

	//设备对象相关
	PWDFDEVICE_INIT device_init = NULL;
	UNICODE_STRING ustring;
	WDF_FILEOBJECT_CONFIG f_cfg;
	WDFDEVICE control_device;
	PDEVICE_OBJECT dev = NULL;
	DEVICE_CONTEXT* dev_ctx = NULL;

	//IO QUEUE相关
	WDF_IO_QUEUE_CONFIG qcfg;

	KdPrint(("DriverEntry [start]\n"));
	

	//初始化WDF_DRIVER_CONFIG
	WDF_DRIVER_CONFIG_INIT(
							   &cfg,
							   NULL //不提供AddDevice函数
							);

	cfg.DriverInitFlags = WdfDriverInitNonPnpDriver;  //指定非pnp驱动
	cfg.DriverPoolTag   = (ULONG)'PEPU';  
	cfg.EvtDriverUnload = EvtDriverUnload;  //指定卸载函数

	//创建一个framework驱动对象,在WDF程序里面,WdfDriverCreate是必须要调用的。
	//framework驱动对象是其他所有wdf对象的父对象,换句话说framework驱动对象是wdf对象树的顶点,它没有父对象了。
	status = WdfDriverCreate(DriverObject,RegistryPath,WDF_NO_OBJECT_ATTRIBUTES,&cfg,&drv);
	if(!NT_SUCCESS(status))
	{
		goto DriverEntry_Complete;
	}

	KdPrint(("Create wdf driver object successfully\n"));


	//创建一个设备

	//先要分配一块内存WDFDEVICE_INIT,这块内存在创建设备的时候会用到。
	device_init = WdfControlDeviceInitAllocate(drv,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);  
	if( device_init == NULL )  
	{    
		status = STATUS_INSUFFICIENT_RESOURCES;    
		goto DriverEntry_Complete;  
	}

	//创建设备的名字,内核模式下,名字类似: L"\\Device\\MyWDF_Device"
	RtlInitUnicodeString(&ustring, MYWDF_KDEVICE);  

	//将设备名字存入device_init中
	status = WdfDeviceInitAssignName(device_init,&ustring);

	if(!NT_SUCCESS(status))
	{
		goto DriverEntry_Complete;
	}

	KdPrint(("Device name Unicode string: %wZ (this name can only be used by other kernel mode code, like other drivers)\n", &ustring));

	//配置FILEOBJECT配置文件,设置FILECREATE,FILECLOSE回调。
	WDF_FILEOBJECT_CONFIG_INIT(&f_cfg,EvtDeviceFileCreate,EvtFileClose,NULL);

	//将FILEOBJECT的设置存入device_init中
	WdfDeviceInitSetFileObjectConfig(device_init,&f_cfg,WDF_NO_OBJECT_ATTRIBUTES);

	//初始化设备属性
//	WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);

	//在设备属性里面增加一个DEVICE_CONTEXT,跟WDF_OBJECT_ATTRIBUTES_INIT相比,WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE
	//会分配一块内存并且存入WDF_OBJECT_ATTRIBUTES里面 (object_attribs.ContextTypeInfo)。DEVICE_CONEXT是个自定义结构。
	WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&object_attribs, DEVICE_CONTEXT);

	
	//根据前面创建的device_init来创建一个设备. (control device)
	status = WdfDeviceCreate(&device_init,&object_attribs,&control_device);
	if(!NT_SUCCESS(status))
	{
		KdPrint(("create device failed\n"));
		goto DriverEntry_Complete;
	}

	//得到设备的上下文
	dev_ctx = WdfObjectGetTypedContext(control_device, DEVICE_CONTEXT);
	RtlZeroMemory(dev_ctx, sizeof(DEVICE_CONTEXT));
	
	//创建IO queue

	//初始化IO QUEUE CONFIG, WdfIoQueueDispatchParallel意思是
	//The framework presents requests to the driver's request handlers as soon as the requests are available. 
	WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&qcfg, WdfIoQueueDispatchParallel);	
	qcfg.PowerManaged = WdfFalse;  //非pnp驱动,无需电源管理。
	qcfg.EvtIoDefault = EvtIoPDPControlDevice; 

	//给设备control_device创建IO QUEUE
	status = WdfIoQueueCreate(control_device,&qcfg,WDF_NO_OBJECT_ATTRIBUTES,&dev_ctx->_DefaultQueue);  
	if( !NT_SUCCESS(status) )  
	{	 
		KdPrint(("Create IoQueue failed, %x\n", status));
		goto DriverEntry_Complete;	
	}
		

	//创建符号连接,这样用户模式下的程序可以使用这个驱动。这个是必须的,不然用户模式下的程序不能访问这个设备。
	RtlInitUnicodeString(&ustring,MYWDF_LINKNAME);	
	status = WdfDeviceCreateSymbolicLink(control_device,&ustring);	
	if( !NT_SUCCESS(status) )  
	{	
		KdPrint(("Failed to create Link\n"));	 
		goto DriverEntry_Complete;  
	}	

	KdPrint(("Create symbolic link successfully, %wZ (user mode code should use this name, like in CreateFile())\n", &ustring));

	WdfControlFinishInitializing(control_device);//创建设备完成。

	/*******************************************
	到这里,我们就成功创建了一个control device。
	control device 是不支持png和power的,而且我们也不需要手工是删除。
	因为framework会帮我们删除,看MSDN
	
	If your driver creates control device objects but does not create framework device objects that support PnP and power management, 
	the driver does not have to delete the control device objects. 
	
	In this case, the framework deletes the control device objects after the driver's EvtDriverUnload callback function returns. 

	更多细节看MSDN,如
	http://msdn.microsoft.com/en-us/library/windows/hardware/ff545424(v=vs.85).aspx
	*******************************************/


	KdPrint(("Create device object successfully\n"));

	


	KdPrint(("DriverEntry succeeds [end]\n"));
	
DriverEntry_Complete:

	return status;
}



static VOID EvtDriverUnload( WDFDRIVER Driver )
{  
	KdPrint(("unload driver\n"));
	KdPrint(("Doesn't need to clean up the devices, since we only have control device here\n"));
}/* EvtDriverUnload */

VOID EvtDeviceFileCreate( __in WDFDEVICE Device, __in WDFREQUEST Request, __in WDFFILEOBJECT FileObject )
{
	KdPrint(("EvtDeviceFileCreate"));

	WdfRequestComplete(Request, STATUS_SUCCESS);
}

VOID EvtFileClose( __in  WDFFILEOBJECT FileObject )
{
	KdPrint(("EvtFileClose"));
}

static VOID EvtIoPDPControlDevice( WDFQUEUE Queue , WDFREQUEST Request )
{
	
	WDF_REQUEST_PARAMETERS Params;  
	WDFREQUEST req;
	NTSTATUS status;
	WDFDEVICE  hDevice;
	DEVICE_CONTEXT* dev_ctx = NULL;

	//buffer
	char* in_buf = NULL;
	char* out_buf = NULL;

	//从WDFREQUEST里面获取参数
	WDF_REQUEST_PARAMETERS_INIT(&Params);  
	WdfRequestGetParameters(Request,&Params);

	KdPrint(("EvtIoPDPControlDevice, type: %x\n", Params.Type));

	//获取input缓冲,如果request类型是WdfRequestTypeRead,那么就返回STATUS_INVALID_DEVICE_REQUEST
	//因为WdfRequestTypeRead类型的Request没有input buffer,只有outputbuffer。
	status = WdfRequestRetrieveInputBuffer(Request, 10, &in_buf, NULL);
	
	KdPrint(("Get input buffer, ret: %x, buffer: %x", status, in_buf));

	//获取output缓冲,如果是WdfRequestTypeWrite,则没有输出缓冲,因为write只有输入缓冲。
	status = WdfRequestRetrieveOutputBuffer(Request, 10, &out_buf, NULL);
	
	KdPrint(("Get output buffer, ret: %x, buffer: %x", status, out_buf));
		
	//获取Queue关联的设备对象
	hDevice = WdfIoQueueGetDevice(Queue);

	//获取设备对象的上下文
	dev_ctx = WdfObjectGetTypedContext(hDevice, DEVICE_CONTEXT);

	KdPrint(("Get the context of current device, %x", dev_ctx));

	
	switch(Params.Type)
	{
		case WdfRequestTypeRead:
			{
				if(out_buf != NULL)
				{
					out_buf[0] = '1';

					KdPrint(("write the data in context buffer into READ request\n"));

					//将设备上下文里面的数据写入Read请求的缓冲里面
					RtlCopyMemory(out_buf, dev_ctx->_Buffer, 10);

					WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, 10);
				}
			}		
			break;
		case WdfRequestTypeWrite:
			{
				if(in_buf != NULL)
				{
					KdPrint(("input buffer, first element: %c\n", in_buf[0]));

					//将write请求的数据写入设备上下文
					RtlCopyMemory(dev_ctx->_Buffer, in_buf, 10);

					WdfRequestComplete(Request, STATUS_SUCCESS);
				}
			}
			break;
		default:
			WdfRequestComplete(Request, STATUS_SUCCESS);
			break;
	}
		

		
	

}






 

你可能感兴趣的:(WDF驱动开发(3)- 用户模式程序和驱动的数据交互)