类封装的驱动程序

类封装的驱动程序

上面的clsInt太过简单了,无法回答这样的问题:在内核中使用类能带来什么好处?simClass工程无法回答上述问题,笔者只是借助它引出并解决一些基本问题。下面我们思考这样一个问题:就驱动本身而言,如何把内核驱动封装成一个类?

内核驱动,无外乎就是一些数据结构:驱动对象、设备对象、文件对象、IRP等;而对这些数据结构的处理就是内核函数:WDM驱动乃是分发函数(Dispatch Function),WDF乃是事件(Event)。

这不正好吗?上述二者恰好是类封装的基本要素!类者,数据加方法。笔者将把诸如驱动对象、设备对象等一切用到的数据结构,作为成员数据;把分发函数或者事件、回调,作为成员函数。一个“驱动类”就此初露峥嵘了。

想法是不错的,但遇到两个问题,下面一一说明。

6.2.1 寻找合适的存储所

定义类之前要解决的第一个问题是,一旦类对象被创建后,它的生命周期基本上要和驱动程序的生命周期相当,在哪里保存类对象呢?创建全局变量当然是一种方法,但存在多个驱动实例时就会发生冲突。在WDM驱动中,有设备扩展可以保存自己的变量。KMDF则更丰富,笔者最终决定在WDFDRIVER对象中保存类对象。达成的效果如图6-5所示。

驱动对象和设备对象是驱动程序的核心,而回调函数又是核心的核心。在图6-5中,驱动对象和设备对象的回调函数,都在DrvClass类中实现。而为了让C++类对象的生命周期和驱动对象保持一致,用一个WDMMEMORY对象将它封装起来,并作为驱动对象的子对象,由框架自动维护,在驱动对象存在时,C++类对象将一直是有效的。

首先看看怎么把一个自定义的内容保存到驱动对象中,这又要用到框架对象的“环境变量”概念了,前面我们学过给设备对象设置环境变量,现在轮到驱动对象了。让我们重新来做一遍。

clip_image002

图6-5 对象模块图

第1步,定义一个获取环境块指针的函数。

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DRIVER_CONTEXT,

GetDriverContext);

上面的宏将定义一个名称为GetDriverContext的函数,这个函数的伪代码如下:

*DRIVER_CONTEXT GetDriverContext(WDFOBJECT Object)

{

// XXX是一个固定的地址,由于未文档化,无法知道其具体定义

return (DRIVER_CONTEXT*)Object->XXX;

}

以后只需要进行如下调用,即能取得驱动对象的环境块指针(前提是传入正确的对象句柄)。

// 获取环境变量

DRIVER_CONTEXT *pContext = GetDriverContext(WdfDriver);

第2步,在WdfDriverCreate创建框架驱动对象的同时,设置环境变量的结构,通过WDF_DRIVER_CONFIG完成。下面代码的前面部分,实现了此步。

第3步,调用GetDriverContext获取环境变量,并将其封装到一个WDFMEMORY对象中,并指定第2步中创建的驱动对象为其父对象,以令框架自动维护其生命周期。下面代码的后面部分,实现了此步。

NTSTATUS DrvClass::DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath)

{

KDBG(DPFLTR_INFO_LEVEL, "DrvClass::DriverEntry");

WDFMEMORY hDriver;

WDF_OBJECT_ATTRIBUTES attributes;

WDF_DRIVER_CONFIG config;

NTSTATUS status = STATUS_SUCCESS;

WDFDRIVER WdfDriver;

// 设定驱动环境块长度

// 宏内部会调用sizeof(…)求结构体长度,并用粘连符(##)获得其名称

WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DRIVER_CONTEXT);

WDF_DRIVER_CONFIG_INIT(&config, DrvClass::PnpAdd_sta);

status = WdfDriverCreate(DriverObject, // WDF驱动对象

RegistryPath,

&attributes,

&config, // 配置参数

&WdfDriver);

// 取得驱动环境块

PDRIVEDR_CONTEXT pContext = GetDriverContext(WdfDriver);

ASSERT(pContext);

pContext->par1 = (PVOID)this;

// 把类对象用WDFMEMORY对象封装后,作为WDFDRIVER对象的子对象

WDF_OBJECT_ATTRIBUTES_INIT(&attributes);

attributes.ParentObject = WdfDriver;

attributes.EvtDestroyCallback = DrvClassDestroy;

WdfMemoryCreatePreallocated(&attributes, (PVOID)this,

sizeof(DrvClass), &hDriver);

KDBG(DPFLTR_INFO_LEVEL, "this = %p", this);

return status;

}

驱动程序将在入口函数DriverEntry中动态创建一个类对象,并即刻调用方法DrvClass::DriverEntry,以创建驱动对象并将其作为对象的存储所。

以这种方法实现的妙处是,对象的维护是自动化的,我们不用操心太切。一切看上去,很是完美。下面是DrvClassDestroy函数的实现,WDF框架会在销毁内存对象时自动调用它,我们在其中销毁类对象。

VOID DrvClassDestroy(IN WDFOBJECT Object)

{

PVOID pBuf = WdfMemoryGetBuffer((WDFMEMORY)Object, NULL);

delete pBuf;

}

6.2.2 类方法与事件函数

KMDF中的事件函数,分开来说:驱动对象有EvtDriverDeviceAdd和EvtDriverUnload,我们将实现前者;设备对象有一系列PNP/Power事件;还有其他对象的事件函数,且忽略之,详见代码。

事件函数说到底是一种回调函数。类普通成员函数,由于编译后会增加this参数,所以无法成为回调函数。只能使用类静态函数,并通过静态函数再回调成员函数。这是一种很通用的实现手段。以EvtDriverDeviceAdd事件函数为例,我们要在类中为它定义两个相关函数。

Class DrvClass

{

// 定义类静态函数,它是全局的,可以作为回调函数

static NTSTATUS PnpAdd_sta(

IN WDFDRIVER Driver,

IN PWDFDEVICE_INIT DeviceInit);

// 再定义类成员函数,将由静态函数内部调用

virtual NTSTATUS PnpAdd(

IN WDFDRIVER Driver,

IN PWDFDEVICE_INIT DeviceInit,

DrvClass* pThis);

// 其他接口函数

// ……

}

要能够通过静态函数回调成员函数,即通过PnpAdd_sta回调PnpAdd函数。前提是要能够获得对象指针,因为我们已经把对象指针保存在驱动对象的环境块中了,所以达到此目的不是难事。代码如下:

NTSTATUS DrvClass::PnpAdd_sta(IN WDFDRIVER Driver,

IN PWDFDEVICE_INIT DeviceInit)

{

// 取得环境块

PDRIVEDR_CONTEXT pContext = GetDriverContext(Driver);

// 环境块中存有对象指针

DrvClass* pThis = (DrvClass*)pContext->par1;

// 再调用成员函数

return pThis->PnpAdd(Driver, DeviceInit);

}

所有其他的事件函数,都必须采用相同的方法实现。

6.2.3 KMDF驱动实现

其实上面的内容,一直是围绕KMDF进行讲解的。DrvClass内部的DriverEntry成员函数已经讲解过了,现在看看真正的入口函数该如何定义吧。

extern "C" NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath

)

{

// 动态创建对象,此步在后面将被修改

DrvClass* myDriver = new(NonPagedPool, 'CY01')DrvClass();

if(myDriver == NULL)return STATUS_UNSUCCESSFUL;

return myDriver->DriverEntry(DriverObject, RegistryPath);

}

干净得不得了,驱动程序在加载之初就以快捷无比的速度向我们定义的类靠拢了。至于第1行代码动态创建对象的操作,当前这样实现已经完全可以了,但在后面将被修改,以支持多态。

6.2.4 WDM驱动实现

如果使用WDM方式进行类封装,对于非PNP类驱动,可以在入口函数中创建控制设备对象,并把类对象保存在设备对象的设备扩展中;对于PNP类驱动,应当在AddDevice函数中建立设备栈时创建类对象,并将其保存在功能设备对象的设备扩展中。笔者会以前者为例,简单讲一下实现。WDMClass示例工程,读者参照代码,在它的基础上很容易扩展出功能更为完善的驱动程序。

这里列出具体的封装过程。首先是类定义,定义一个通用的分发函数如下:

class WDMDrvClass{

public:

static NTSTATUS DispatchFunc_sta(

DEVICE_OBJECT Device,

PIRP Irp);

virtual NTSTATUS DispatchFunc(

DEVICE_OBJECT Device,

PIRP Irp);

// 其他……

};

同理,定义一个静态函数和一个类成员函数,静态函数将通过对象指针调用成员函数。入口函数中要这样定义:

typedef struct{

WDMDrvClass pThis;

//……

}DEVICE_EXTENSION;

NTSTATUS DriverEntry( PDRIVER_OBJECT Driver,

PUNICODE_STRING Register)

{

// 创建动态对象

WDMDrvClass* pDrv = new(NonPagedPool, 'SAMP') WDMDrvClass();

// 设置分发函数,全部指向DispatchFunc_sta

for(int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)

Driver->DispatchFunction[i] = pDrv->DispatchFunc_sta;

}

// 创建控制设备对象,并同时创建设备扩展区

IoCreateDeviceObject(..., sizeof(DEVICE_EXTENSION));

// 把对象指针保存到设备扩展中

DEVICE_EXTENSION* pContext = (DEVICE_EXTENSION*)DeviceObject->DeviceExtension;

pContext->pThis = pDrv;

return STATUS_SUCCESS;

}

这一切就绪之后,我们还是来看看DispatchFunc_sta该如何实现吧。诚如我们所知,所有的驱动分发函数的第一个参数总是设备对象,正是我们所创建的那个。通过它,我们总是能够在静态函数中得到对象指针。下面是DispatchFunc_sta函数的实现。

NTSTATUS WDMDrvClass::DispatchFunc_sta(

DEVICE_OBJECT Device, PIRP Irp)

{

PDEVICE_EXTENSION pContext = Device->DeviceExtension;

WDMDrv pThis = pContext->pThis;

return pThis-> DispatchFunc(Device, Irp);

}

与上述KMDF的实现类似,其他更详细的实现内容,请参阅工程代码。

你可能感兴趣的:(封装)