• 类封装的驱动程序


    类封装的驱动程序

    上面的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的实现类似,其他更详细的实现内容,请参阅工程代码。

  • 相关阅读:
    为什么说2013是PHP年
    wordpress 投稿插件 支持图片上传
    php简易页面内调试技巧
    WordPress中文文档
    百度网盘文件直链
    HOWTO:如何解决安装包在系统“添加/删除”中无法修复或卸载的问题
    InstallShield 2008 终止声明 (EOL)对最终客户意味着什么
    InstallShield 2011新功能试用(10) Express版本
    AdminStudio 9.5 Service Pack 3
    INFO:InstallShield中安装路径变量的区别
  • 原文地址:https://www.cnblogs.com/broadview/p/1956635.html
Copyright © 2020-2023  润新知