WDF里面,大多数对象都支持自定义的数据,比如给设备对象创建一个context。
对象上下文
先自定义一个结构,比如
typedef struct { WDFQUEUE _DefaultQueue; }DEVICE_CONTEXT;
里面放了一个对象WDFQUEUE. 然后给设备对象创建一个上下文内存块。在使用之前先要声明一下这个结构,相当于告诉框架,我们需要使用一个context。
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT);
如果不需要context,那么可以这么初始化一个设备属性,WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);
如果需要context,那么要换个函数,WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&object_attribs, DEVICE_CONTEXT); 这样就会分配一个DEVICE_CONTEXT的内存块,并且将内存块的指针保存到WDF_OBJECT_ATTRIBUTES里面。看一下WDF_OBJECT_ATTRIBUTES的定义:
typedef struct _WDF_OBJECT_ATTRIBUTES { ULONG Size; PFN_WDF_OBJECT_CONTEXT_CLEANUP EvtCleanupCallback; PFN_WDF_OBJECT_CONTEXT_DESTROY EvtDestroyCallback; WDF_EXECUTION_LEVEL ExecutionLevel; WDF_SYNCHRONIZATION_SCOPE SynchronizationScope; WDFOBJECT ParentObject; size_t ContextSizeOverride; PCWDF_OBJECT_CONTEXT_TYPE_INFO ContextTypeInfo; } WDF_OBJECT_ATTRIBUTES, *PWDF_OBJECT_ATTRIBUTES;
最后一个成员ContextTypeInfo就指向分配出来的context内存块。
可以根据对象来得到这个context:
dev_ctx = WdfObjectGetTypedContext(control_device, DEVICE_CONTEXT); RtlZeroMemory(dev_ctx, sizeof(DEVICE_CONTEXT));
上面2行代码的意思就是获取设备对象control_device的上下文内存块,并且清零。
IO QUEUE,存放WDFREQUEST的队列
WDF提供了一个对象WDFREQUEST, WDFREQUEST其实是对WDM里面IRP的一个封装。WDF还提供了另外一个对象也就是WDFQUEUE,这个队列会存放IRP请求。
我们可以给设备对象control_device创建一个IOQUEUE,比如:(WDFQUEUE存放在了设备对象的上下文里面)
默认队列不能使用WdfIoQueueDispatchManual,不然创建queue会失败。这个我试过。
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&qcfg, WdfIoQueueDispatchParallel); qcfg.PowerManaged = WdfFalse; //非pnp驱动,也就无需电源管理了. qcfg.EvtIoDefault = EvtIoPDPControlDevice; //创建一个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; }
注意, 需要在WdfControlFinishInitializing(control_device);之前创建IOQUEUE. 我试过在WdfControlFinishInitializing(control_device);后面创建,会失败。
在这个例子里面,我们提供了一个EvtIoDefault的回调函数,简单实现一下,就是打印一下WDFREQUEST的type并且完成这个请求。
static VOID EvtIoPDPControlDevice( WDFQUEUE Queue , WDFREQUEST Request ) { // WDF_REQUEST_PARAMETERS Params; WDF_REQUEST_PARAMETERS_INIT(&Params); WdfRequestGetParameters(Request,&Params); KdPrint(("EvtIoPDPControlDevice, type: %x\n", Params.Type)); WdfRequestComplete(Request, STATUS_SUCCESS); }
完整代码:
#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; }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 ) { //从WDFREQUEST里面获取参数 WDF_REQUEST_PARAMETERS Params; WDF_REQUEST_PARAMETERS_INIT(&Params); WdfRequestGetParameters(Request,&Params); KdPrint(("EvtIoPDPControlDevice, type: %x\n", Params.Type)); WdfRequestComplete(Request, STATUS_SUCCESS); }
简单调用一下:
// 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; BOOL b = ReadFile(hDevice, buf, 10, &dwRet, NULL); wprintf(L"call ReadFile, ret: %d\n", b); b = WriteFile(hDevice, buf, 10, &dwRet, NULL); wprintf(L"call WriteFile, ret: %d\n", b); CloseHandle(hDevice); wprintf(L"Closed handle\n"); } return 0; }
可以看到:
驱动打印出了2个IO, 一个类型是3,一个是4. 查看一下msdn,3确实是READ, 4是WRITE,正确。也就是说用户模式代码引起了驱动调用2次回调EvtIoPDPControlDevice。这正是我们想要的,没有问题。
typedef enum _WDF_REQUEST_TYPE { WdfRequestTypeCreate = 0x0, WdfRequestTypeCreateNamedPipe = 0x1, WdfRequestTypeClose = 0x2, WdfRequestTypeRead = 0x3, WdfRequestTypeWrite = 0x4, WdfRequestTypeQueryInformation = 0x5, WdfRequestTypeSetInformation = 0x6, WdfRequestTypeQueryEA = 0x7, WdfRequestTypeSetEA = 0x8, WdfRequestTypeFlushBuffers = 0x9, WdfRequestTypeQueryVolumeInformation = 0xa, WdfRequestTypeSetVolumeInformation = 0xb, WdfRequestTypeDirectoryControl = 0xc, WdfRequestTypeFileSystemControl = 0xd, WdfRequestTypeDeviceControl = 0xe, WdfRequestTypeDeviceControlInternal = 0xf, WdfRequestTypeShutdown = 0x10, WdfRequestTypeLockControl = 0x11, WdfRequestTypeCleanup = 0x12, WdfRequestTypeCreateMailSlot = 0x13, WdfRequestTypeQuerySecurity = 0x14, WdfRequestTypeSetSecurity = 0x15, WdfRequestTypePower = 0x16, WdfRequestTypeSystemControl = 0x17, WdfRequestTypeDeviceChange = 0x18, WdfRequestTypeQueryQuota = 0x19, WdfRequestTypeSetQuota = 0x1A, WdfRequestTypePnp = 0x1B, WdfRequestTypeOther = 0x1C, WdfRequestTypeUsb = 0x40, WdfRequestTypeNoFormat = 0xFF, WdfRequestTypeMax = 0x100 } WDF_REQUEST_TYPE;