Windows驱动程序框架

windows驱动程序入门比较坑爹一点,本文旨在降低入门的门槛。注:下面的主要以NT式驱动为例,部分涉及到WDM驱动的差别会有特别说明。

首先,肯定是配置好对应的开发环境啦,不懂的就百度下吧,这里不再次描述了。

在Console控制台下,我们的有一个入口函数main;在Windows图形界面平台下,有另外一个入口函数Winmain。我们只要在这入口函数里面调用其他相关的函数,程序就会按照我们的意愿跑起来了。在我们用IDE开发的时候,也许你不会发现这些细微之处是如何配置出来的,一般来说我们也不用理会,因为在新建工程的时候,IDE已经帮我们把编译器(Compiler)以及连接器(Linker)的相关参数设置好,在正式编程的时候,我们只要按照规定的框架编程就行了。

同样,在驱动程序也有一个入口函数DriverEntry,这并不是一定的,但这是微软默认的、推荐使用的。在我们配置开发环境的时候我们有机会指定入口函数,这是链接器的参数/entry:"DriverEntry"。

入口函数的声明
代码:
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)  
DriverEntry主要是对驱动程序进行初始化工作,它由系统进程(System)创建,系统启动的时候System系统进程就被创建了。

驱动加载的时候,系统进程将会创建新的线程,然后调用执行体组件中的对象管理器,创建一个驱动对象(DRIVER_OBJECT)。另外,系统进程还得调用执行体组件中的配置管理程序,查询此驱动程序在注册表中对应项。系统进程在调用驱动程序的DriverEntry的时候就会将这两个值传到pDriverObject和pRegistryPath。

接下来,我们介绍下上面出现的几个数据结构:

typedef LONG NTSTATUS  

在驱动开发中,我们应习惯于用NTSTATUS返回信息,NTSTATUS各个位有不同的含义,我们可以也应该用宏NT_SUCCESS来判断是否返回成功。

代码:
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)  
NTSTAUS的编码意义:
名称:  image001.png查看次数: 2文件大小:  4.6 KB
其中
Ser是Serviity的缩写,代表严重程度。
00:成功      01:信息     10:警告      11:错误
C是Customer的缩写,代表自定义的位。
Facility:设备位
Code:设备的状态代码。

根据这定义编码,还有补码的概念,那么只要是错误的时候,最高位就是1,NTSTATUS的值就是负数,所以可以大于零来判断,但无论如何都希望读者用NT_SUCCESS宏来判断是否成功,因为这可能在以后会有所改动,即使这么多年来都一直沿用着。

同样的,微软也为我们定义了其他几个判断宏:
代码:
#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1)  
#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2)  
#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)  
有了之前的介绍,这三个相信不说大家也能领会了。但最常用的还是NT_SUCCESS。

我们继续说其他的两个数据结构,先说PUNICODE_STRING吧,P代表这是一个指针类型,指向一个UNICODE_STRING结构。

宽字符串结构体(UNICODE_STRING)
代码:
typedef struct _UNICODE_STRING {  
  USHORT  Length;  
  USHORT  MaximumLength;  
  PWSTR  Buffer;  
} UNICODE_STRING, *PUNICODE_STRING;  
其中,
Ø  Length:Unicode字符串当前的字符长度。注意不是字节数,每个Unicode字符占用两个字节。
Ø  MaximumLength:该Unicode字符串的最大容纳长度。
Ø  Buffer:Unicode字符串的缓冲地址。
 
UNICODE_STRING是Windows驱动开发里面经常用到的一个结构,用Length来标记字符串的长度而不再用\0来表示结束。可以用RtlInitUnicodeString来对其初始化,但这里的pRegistryPath是直接由创建驱动程序的线程传进来的参数,如果在接下来仍需要用到该值,最好是使用RtlCopyUnicodeString函数将其值另外保存下来,因为这个字符串并不是长期存在的,DriverEntry函数返回的时候可能就会被销毁了。
 
PDRIVER_OBJECT,P代表这是一个指针类型,指向一个驱动对象(DRIVER_OBJECT),每个驱动程序都有一个驱动对象。这是一个半透明的数据结构,微软没有公开它的完全定义,只是有提到几个成员,但我们依旧可以通过WinDbg看到它的定义,只是不同的系统可能会存在不同的结构。不过我另外在WDK的头文件WDM.h里面发现了它的定义:
 驱动对象(DRIVER_OBJECT)
代码:
typedef struct _DRIVER_OBJECT {  
    CSHORT Type;  
    CSHORT Size;  
    PDEVICE_OBJECT DeviceObject;  
    ULONG Flags;  
    PVOID DriverStart;  
    ULONG DriverSize;  
    PVOID DriverSection;  
    PDRIVER_EXTENSION DriverExtension;  
    UNICODE_STRING DriverName;  
    PUNICODE_STRING HardwareDatabase;  
    PFAST_IO_DISPATCH FastIoDispatch;  
    PDRIVER_INITIALIZE DriverInit;  
    PDRIVER_STARTIO DriverStartIo;  
    PDRIVER_UNLOAD DriverUnload;  
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];  
} DRIVER_OBJECT;  
这里提下几个比较重要的字段,
Ø  DeviceObject:指向由此驱动创建的设备对象。每个驱动程序都会有一个或多个的设备对象。其中,每个设备对象都会有一个指针指向下一个设备对象,这在我们介绍设备对象的时候再继续说。
Ø  DriverName:驱动的名字,该字符串一般为\Driver\[驱动程序名称]。
Ø  HardwareDatabase:记录设备的硬件数据库键名。该字符串一般为"\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\[服务名]"。
Ø  FastIoDispatch:指向快速I/O函数入口,是文件驱动中用到的排遣函数。
Ø  DriverStartIo:记录StartIo例程的函数地址,用于串行化操作。
Ø  DriverUnload:指定驱动卸载时所用的回调函数地址。
Ø  MajorFunction:这是一个函数指针数组,每个指针指向的是一个函数,该函数就是处理相应IRP的排遣函数,数组的索引值与IRP_MJ_XXX相对应。
 
我们已经了解了DriverEntry函数头的那个数据结构了,但这还不够,在DriverEntry里,我们主要是对驱动程序进行初始化,这就涉及到其他的一些数据结构了,下面我们继续逐一地介绍。
 
设备对象(DEVICE_OBJECT)
代码:
typedef struct _DEVICE_OBJECT {  
  CSHORT                      Type;  
  USHORT                      Size;  
  LONG                        ReferenceCount;  
  struct _DRIVER_OBJECT  *DriverObject;  
  struct _DEVICE_OBJECT  *NextDevice;  
  struct _DEVICE_OBJECT  *AttachedDevice;  
  struct _IRP  *CurrentIrp;  
  PIO_TIMER                   Timer;  
  ULONG                       Flags;  
  ULONG                       Characteristics;  
  __volatile PVPB             Vpb;  
  PVOID                       DeviceExtension;  
  DEVICE_TYPE                 DeviceType;  
  CCHAR                       StackSize;  
  union {  
    LIST_ENTRY         ListEntry;  
    WAIT_CONTEXT_BLOCK Wcb;  
  } Queue;  
  ULONG                       AlignmentRequirement;  
  KDEVICE_QUEUE               DeviceQueue;  
  KDPC                        Dpc;  
  ULONG                       ActiveThreadCount;  
  PSECURITY_DESCRIPTOR        SecurityDescriptor;  
  KEVENT                      DeviceLock;  
  USHORT                      SectorSize;  
  USHORT                      Spare1;  
  struct _DEVOBJ_EXTENSION  *  DeviceObjectExtension;  
  PVOID                       Reserved;  
} DEVICE_OBJECT, *PDEVICE_OBJECT;  
这里只对几个比较重要的字段进行说明:
Ø  DriverObject:指向创建此设备对象的驱动程序对象。同属于一个驱动程序的设备对象指向的是同一个驱动对象。
Ø  NextObject:指向同一个驱动程序创建的下一个设备对象。同一个驱动对象可以创建若干个设备对象,每个设备对象根据NextDevice来连成一个链表,最后一个设备对象的NextDevice域为NULL。
Ø  AttachedDevice:指向附加到此设备对象之上的最近设备对象。这里需要理解分层驱动程序的概念。
Ø  DeviceExtension:指向设备的扩展对象。每个设备都会指定一个设备扩展对象,这个数据结构由驱动程序开发者自行定义,可以用来记录一些与设备相关的一些信息,同时应尽量避免使用全局变量,将数据存放在设备扩展里,具有很大的灵活性。
Ø  CurrentIrp:在使用StartIO例程的时候,该成员指向的是当前IRP结构。
Ø  Flags:指定了该设备对象的标记。下面列出了常用的几个标记:
名称:  2.png查看次数: 1文件大小:  10.3 KB
Ø  DeviceType:指定设备的类型。一般在开发虚拟设备时,选择FILE_DEVICE_UNKNOW。其他的请自行参考WDK文档。
Ø  StackSize:在多层驱动情况下,驱动与驱动之间会形成类似堆栈的结构,称之为设备栈。IRP会依次从最高层传递到最底层。StackSize描述的就是该层数。最底层的设备层数为1。
Ø  AlignmentRequirement:在进行大容量传输的时候,往往需要进行内存对齐,以保证传输速度。请使用类似FILE_XXX_ALIGNMENT的方式进行赋值。

下面给大家展示一下DriverEntry的最基本框架:
代码:
#ifdef __cplusplus
extern "C"
{
#endif

#include <NTDDK.h>

#ifdef __cplusplus
};
#endif

#define PAGECODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")


typedef struct _DEVICE_EXTENSION
{
  PDEVICE_OBJECT pDevice;
  UNICODE_STRING ustrDeviceName;
  UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION , *PDEVICE_EXTENSION;

//函数声明
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *DeviceObject, __in struct _IRP  *Irp);
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);

///
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
                PUNICODE_STRING pRegistryPath)
{
  NTSTATUS status;
  PDEVICE_EXTENSION pDevExt;
  PDEVICE_OBJECT pDevObj;
  KdPrint(("\n--------------------------------------!\n"));
  KdPrint(("Enter DriverEntry!\n"));
  //注册相关例程
  pDriverObject->DriverUnload = UnloadRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CREATE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_READ]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_WRITE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE]  =  DispatchRoutine;

  //初始化相关字符串
  UNICODE_STRING ustrDeviceName;  //设备名
  UNICODE_STRING ustrSymLinkName; //符号链接名

  RtlInitUnicodeString(&ustrDeviceName,L"\\Device\\MyDDKDevice1");
  RtlInitUnicodeString(&ustrSymLinkName,L"\\??\\MyDDKDriver1");

  //创建设备对象
  
  status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&ustrDeviceName,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevObj);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("Create Device Failure!\n"));
    return status;  
  }
  pDevObj->Flags |= DO_BUFFERED_IO;
  pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
  pDevExt->ustrDeviceName = ustrDeviceName;
  pDevExt->pDevice = pDevObj;

  //创建符号链接
  pDevExt->ustrSymLinkName = ustrSymLinkName;
  status = IoCreateSymbolicLink(&ustrSymLinkName,&ustrDeviceName);
  if (!NT_SUCCESS(status))
  {
    IoDeleteDevice(pDevObj);
    return status;
  }
  KdPrint(("Leave DriverEntry! stauts=%d",status));
  return status;
}

这是用C++写的,所以必要的地方加上了extern“C”,否则会引起一些错误,这是因为C++与C在进行名称粉碎的时候处理得不一样,C++这个改进主要是为了实现一些高级功能,比如多态。虽然加上extern “C”会有点麻烦,但可以用上C++那些功能,个人觉得也有所值。如果用C,直接忽略上面的extern “C”。
 
NTDDK.h是NT式驱动需要加载的头文件,如果是WDM式驱动,那么加载的是WDM.h
 
#define INITCODE code_seg("INIT")定义一个宏,#prama INITCODE还原后就是#pramacode_seg(“INIT”),表示接下来的代码加载到INIT内存区域中,成功加载后,可以退出内存。对于DriverEntry这种一次性的函数而言,这是最适合的选择,可以节省内存。函数结束后需要显式地切换回来,如:#prama LOCKEDCODE。

同样,PAGECODE表示分页内存,作用是将此部分代码放入分页内存中运行,在里面的代码切换进程上下文时可能会被换回分页文件。LOCKEDCODE表示默认内存,也就是非分页内存,里面的代码常驻内存。IRQL处于DISPATCH_LEVEL或者以上的等级,必须处于非分页内存里面。

同理,对于数据段也有同样的机制,于是有了PAGEDATA、LOCKEDDATA、INITDATA。
 
KdPrint是一个宏,在调试版本(Checked)里面(具备DBG宏定义),有
代码:
#define KdPrint(_x_) DbgPrint _x_  

而在正式版本(Free)里面,KdPrint被定义为空。所以可以用来作为调试输出。但注意Kdprint后面是两层括号,用法与C语言运行库的printf差不多。

pDriverObject->DriverUnload = UnloadRoutine;将卸载例程函数告诉驱动对象,驱动对象在前面已经有定义,这里不做深入讨论。

pDriverObject->MajorFunction[IRP_MJ_CREATE]  =       DispatchRoutine;注册排遣例程。Windows是消息驱动,而驱动程序是IRP驱动的,I/O管理器将发送到驱动的“消息”封装在IRP里面,驱动程序也将结果告诉IRP。类似windows的消息机制,对于不同的“消息”,驱动程序需要注册不同的处理例程来区别对待,当然也可以放在同一个例程里面,然后用switch语句来区别对待,但当处理过程比较长的时候,会比较凌乱。

IRP_MJ_CREATE是当RING3应用程序在使用CreateFile函数建立与驱动程序的通信通道时所激活的。IRP_MJ_READ是ReadFile,IRP_MJ_WRITE是WriteFile,而IRP_MJ_CLOSE是CloseHandle关闭文件句柄的时候产生的。

小知识:对于WDM式驱动,仍需要注册AddDevice例程,pDriverObject->DriverExtension->AddDevice = WDMAddDeviceRoutine,设备对象的初始化将在AddDevice里面进行而不是DriverEntry。另外还需要注册IRP_MJ_PNP排遣函数。

前面有讲到UNICODE_STRING结构,那么这里就可以很好的了解初始化的这两个结构了,忘记的看回前面的,这里只列出RtlInitUnicodeString函数定义。

代码:
VOID RtlInitUnicodeString(PUNICODE_STRING  DestinationString,PCWSTR  SourceString);  
IoCreateDevice是注册设备对象,一个驱动必须对应这一个或多个设备对象。

代码:
NTSTATUS   
  IoCreateDevice(  
    IN PDRIVER_OBJECT  DriverObject,  
    IN ULONG  DeviceExtensionSize,  
    IN PUNICODE_STRING  DeviceName  OPTIONAL,  
    IN DEVICE_TYPE  DeviceType,  
    IN ULONG  DeviceCharacteristics,  
    IN BOOLEAN  Exclusive,  
    OUT PDEVICE_OBJECT  *DeviceObject  
    );  
Ø  DriverObject:驱动对象的指针,这里用的是入口函数传进来的驱动对象。每个驱动有若干个设备对象,每个设备对象只有一个驱动函数。
Ø  DeviceExtensionSize:自定义的设备扩展的大小。
Ø  DeviceName:设备对象名称,前面有用RtlInitUnicodeString对其进行过初始化。设备对象是暴露在内核层面上的名称,对于RING3层桌面程序是不可见的。格式需为:\Device\[设备名]。如果不指定设备名,I/O管理器将会自动分配一个数字作为设备名,如:\Device\00000001
Ø  DeviceType:设备类型,这里用FILE_DEVICE_UNKNOWN。
Ø  DeviceCharacteristics:设备对象的特征。
Ø  Exclusive:设置设备对象是否为专用的。也即是否允许有第二个驱动、程序访问这个设备对象。
Ø  DeviceObject:I/O管理器将会负责创建这个设备对象,可以用这个参数来接收该对象的地址。
创建了设备对象,在程序临近结束之际需要用IoDeleteDevice删除设备对象。
 
pDevObj->Flags |= DO_BUFFERED_IO;是设置访问标志为缓冲模式。
 
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;获取设备扩展。
 
pDevExt->ustrDeviceName = ustrDeviceName;将相关信息保存在设备扩展里面。
 
设备对象名称只暴露在内核层面,想要在RING3用户层访问驱动程序则需要创建符号链接。符号链接是暴露在用户层面的。注册符号链接用的函数是IoCreateSymbolicLink。

代码:
NTSTATUS 
  IoCreateSymbolicLink(
    IN PUNICODE_STRING  SymbolicLinkName,
    IN PUNICODE_STRING  DeviceName
    );
Ø  SymbolicLinkName:符号链接的字符串,前面有对其初始化。符号链接名需要以\??\开头,或者\DosDevice\(未证实)。而在用户模式下需要以\\.\开头才能找到对应的符号链接。
Ø  DeviceName:设备名的字符串。
注意:创建了符号链接,在程序临近结束之际需要用IoDeleteSymbolicLink删除符号链接。并且先删除符号链接在删除设备对象。
 
在上面的DriverEntry函数里面,已经完成了基本的初始化工作,接下来,我们看一下卸载回调例程。
 
回调例程的声明为:
代码:
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);  
除了函数名字外,请不要改动其他参数。
 
在回调函数里面,我们主要进行一些清理工作。
代码:
#pragma PAGECODE
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *pDriverObject)
{
  PDEVICE_OBJECT pNextObj;
  PDEVICE_EXTENSION pDevExt;
  pNextObj = pDriverObject->DeviceObject;
  KdPrint(("Enter unload routine!\n"));
  while(pNextObj != NULL)
  {
    pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;

    //删除符号链接
    IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
    pNextObj = pNextObj->NextDevice;
    //删除设备
    IoDeleteDevice(pDevExt->pDevice);
  }
  KdPrint(("Leave unload routine!\n"));
  KdPrint(("--------------------------------------!\n"));
}
基本在上面都已经有所描述了。这里还强调一点,一个驱动程序可以有一个或者多个设备对象,在驱动程序完全卸载之前需要删除对应的符号链接、设备对象。所以这里用到了while循环来完成这项工作,不明白的回去上面继续熟悉下设备对象的结构。
 
这个驱动程序没有做什么工作,所以在排遣函数里面我们只是单纯的设置状态为成功,操作的字节为0,设置IRP状态为完成,就返回了。

代码:
#pragma PAGECODE
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *pDeviceObject,
             __in struct _IRP  *pIrp)
{
  KdPrint(("Enter dispatch routine!\n"));
  NTSTATUS status = STATUS_SUCCESS;
  //完成IRP
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = 0;
  IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  KdPrint(("Leave dispatch routine!\n"));
  return status;
}
pIrp->IoStatus.Status = status;设置IO状态。

pIrp->IoStatus.Information = 0;设置实际操作的字节数。用户层函数ReadFile、WriteFile的第四个参数lpNumberOfBytesRead用于接收实际操作的字节数,这个结果就是这样产生的。

IoCompleteRequest设置完成IRP的处理,否则会继续往下层传递。
 
整一个框架都已基本介绍完毕了,下面贴上完整的代码吧。
代码:
#ifdef __cplusplus
extern "C"
{
#endif

#include <NTDDK.h>

#ifdef __cplusplus
};
#endif

#define PAGECODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

#define arrarysize(arr) (sizeof(arr)/sizeof(arr)[0])

typedef struct _DEVICE_EXTENSION
{
  PDEVICE_OBJECT pDevice;
  UNICODE_STRING ustrDeviceName;
  UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION , *PDEVICE_EXTENSION;


//函数声明
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *DeviceObject, __in struct _IRP  *Irp);
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);
    

///
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
                PUNICODE_STRING pRegistryPath)
{
  NTSTATUS status;
  PDEVICE_EXTENSION pDevExt;
  PDEVICE_OBJECT pDevObj;
  KdPrint(("\n--------------------------------------!\n"));
  KdPrint(("pRegistryPath value:%ws",pRegistryPath));
  KdPrint(("Enter DriverEntry!\n"));
  //注册相关例程
  pDriverObject->DriverUnload = UnloadRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CREATE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_READ]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_WRITE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE]  =  DispatchRoutine;

  //初始化相关字符串
  UNICODE_STRING ustrDeviceName;  //设备名
  UNICODE_STRING ustrSymLinkName; //符号链接名

  RtlInitUnicodeString(&ustrDeviceName,L"\\Device\\MyDDKDevice1");
  RtlInitUnicodeString(&ustrSymLinkName,L"\\??\\MyDDKDriver1");

  //创建设备对象
  
  status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&ustrDeviceName,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevObj);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("Create Device Failure!\n"));
    return status;  
  }
  pDevObj->Flags |= DO_BUFFERED_IO;
  pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
  pDevExt->ustrDeviceName = ustrDeviceName;
  pDevExt->pDevice = pDevObj;

  //创建符号链接
  pDevExt->ustrSymLinkName = ustrSymLinkName;
  status = IoCreateSymbolicLink(&ustrSymLinkName,&ustrDeviceName);
  if (!NT_SUCCESS(status))
  {
    IoDeleteDevice(pDevObj);
    return status;
  }
  KdPrint(("Leave DriverEntry! stauts=%d",status));
  return status;
}
#pragma PAGECODE
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *pDeviceObject,
             __in struct _IRP  *pIrp)
{
  KdPrint(("Enter dispatch routine!\n"));
  NTSTATUS status = STATUS_SUCCESS;
  //完成IRP
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = 0;
  IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  KdPrint(("Leave dispatch routine!\n"));
  return status;
  
}

#pragma PAGECODE
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *pDriverObject)
{
  PDEVICE_OBJECT pNextObj;
  PDEVICE_EXTENSION pDevExt;
  pNextObj = pDriverObject->DeviceObject;
  KdPrint(("Enter unload routine!\n"));
  while(pNextObj != NULL)
  {
    pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;

    //删除符号链接
    IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
    pNextObj = pNextObj->NextDevice;
    //删除设备
    IoDeleteDevice(pDevExt->pDevice);
  }
  KdPrint(("Leave unload routine!\n"));
  KdPrint(("--------------------------------------!\n"));
}
原文链接:http://bbs.pediy.com/showthread.php?p=1208991

首先,肯定是配置好对应的开发环境啦,不懂的就百度下吧,这里不再次描述了。

在Console控制台下,我们的有一个入口函数main;在Windows图形界面平台下,有另外一个入口函数Winmain。我们只要在这入口函数里面调用其他相关的函数,程序就会按照我们的意愿跑起来了。在我们用IDE开发的时候,也许你不会发现这些细微之处是如何配置出来的,一般来说我们也不用理会,因为在新建工程的时候,IDE已经帮我们把编译器(Compiler)以及连接器(Linker)的相关参数设置好,在正式编程的时候,我们只要按照规定的框架编程就行了。

同样,在驱动程序也有一个入口函数DriverEntry,这并不是一定的,但这是微软默认的、推荐使用的。在我们配置开发环境的时候我们有机会指定入口函数,这是链接器的参数/entry:"DriverEntry"。

入口函数的声明
代码:
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)  
DriverEntry主要是对驱动程序进行初始化工作,它由系统进程(System)创建,系统启动的时候System系统进程就被创建了。

驱动加载的时候,系统进程将会创建新的线程,然后调用执行体组件中的对象管理器,创建一个驱动对象(DRIVER_OBJECT)。另外,系统进程还得调用执行体组件中的配置管理程序,查询此驱动程序在注册表中对应项。系统进程在调用驱动程序的DriverEntry的时候就会将这两个值传到pDriverObject和pRegistryPath。

接下来,我们介绍下上面出现的几个数据结构:

typedef LONG NTSTATUS  

在驱动开发中,我们应习惯于用NTSTATUS返回信息,NTSTATUS各个位有不同的含义,我们可以也应该用宏NT_SUCCESS来判断是否返回成功。

代码:
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)  
NTSTAUS的编码意义:
名称:  image001.png查看次数: 2文件大小:  4.6 KB
其中
Ser是Serviity的缩写,代表严重程度。
00:成功      01:信息     10:警告      11:错误
C是Customer的缩写,代表自定义的位。
Facility:设备位
Code:设备的状态代码。

根据这定义编码,还有补码的概念,那么只要是错误的时候,最高位就是1,NTSTATUS的值就是负数,所以可以大于零来判断,但无论如何都希望读者用NT_SUCCESS宏来判断是否成功,因为这可能在以后会有所改动,即使这么多年来都一直沿用着。

同样的,微软也为我们定义了其他几个判断宏:
代码:
#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1)  
#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2)  
#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)  
有了之前的介绍,这三个相信不说大家也能领会了。但最常用的还是NT_SUCCESS。

我们继续说其他的两个数据结构,先说PUNICODE_STRING吧,P代表这是一个指针类型,指向一个UNICODE_STRING结构。

宽字符串结构体(UNICODE_STRING)
代码:
typedef struct _UNICODE_STRING {  
  USHORT  Length;  
  USHORT  MaximumLength;  
  PWSTR  Buffer;  
} UNICODE_STRING, *PUNICODE_STRING;  
其中,
Ø  Length:Unicode字符串当前的字符长度。注意不是字节数,每个Unicode字符占用两个字节。
Ø  MaximumLength:该Unicode字符串的最大容纳长度。
Ø  Buffer:Unicode字符串的缓冲地址。
 
UNICODE_STRING是Windows驱动开发里面经常用到的一个结构,用Length来标记字符串的长度而不再用\0来表示结束。可以用RtlInitUnicodeString来对其初始化,但这里的pRegistryPath是直接由创建驱动程序的线程传进来的参数,如果在接下来仍需要用到该值,最好是使用RtlCopyUnicodeString函数将其值另外保存下来,因为这个字符串并不是长期存在的,DriverEntry函数返回的时候可能就会被销毁了。
 
PDRIVER_OBJECT,P代表这是一个指针类型,指向一个驱动对象(DRIVER_OBJECT),每个驱动程序都有一个驱动对象。这是一个半透明的数据结构,微软没有公开它的完全定义,只是有提到几个成员,但我们依旧可以通过WinDbg看到它的定义,只是不同的系统可能会存在不同的结构。不过我另外在WDK的头文件WDM.h里面发现了它的定义:
 驱动对象(DRIVER_OBJECT)
代码:
typedef struct _DRIVER_OBJECT {  
    CSHORT Type;  
    CSHORT Size;  
    PDEVICE_OBJECT DeviceObject;  
    ULONG Flags;  
    PVOID DriverStart;  
    ULONG DriverSize;  
    PVOID DriverSection;  
    PDRIVER_EXTENSION DriverExtension;  
    UNICODE_STRING DriverName;  
    PUNICODE_STRING HardwareDatabase;  
    PFAST_IO_DISPATCH FastIoDispatch;  
    PDRIVER_INITIALIZE DriverInit;  
    PDRIVER_STARTIO DriverStartIo;  
    PDRIVER_UNLOAD DriverUnload;  
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];  
} DRIVER_OBJECT;  
这里提下几个比较重要的字段,
Ø  DeviceObject:指向由此驱动创建的设备对象。每个驱动程序都会有一个或多个的设备对象。其中,每个设备对象都会有一个指针指向下一个设备对象,这在我们介绍设备对象的时候再继续说。
Ø  DriverName:驱动的名字,该字符串一般为\Driver\[驱动程序名称]。
Ø  HardwareDatabase:记录设备的硬件数据库键名。该字符串一般为"\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\[服务名]"。
Ø  FastIoDispatch:指向快速I/O函数入口,是文件驱动中用到的排遣函数。
Ø  DriverStartIo:记录StartIo例程的函数地址,用于串行化操作。
Ø  DriverUnload:指定驱动卸载时所用的回调函数地址。
Ø  MajorFunction:这是一个函数指针数组,每个指针指向的是一个函数,该函数就是处理相应IRP的排遣函数,数组的索引值与IRP_MJ_XXX相对应。
 
我们已经了解了DriverEntry函数头的那个数据结构了,但这还不够,在DriverEntry里,我们主要是对驱动程序进行初始化,这就涉及到其他的一些数据结构了,下面我们继续逐一地介绍。
 
设备对象(DEVICE_OBJECT)
代码:
typedef struct _DEVICE_OBJECT {  
  CSHORT                      Type;  
  USHORT                      Size;  
  LONG                        ReferenceCount;  
  struct _DRIVER_OBJECT  *DriverObject;  
  struct _DEVICE_OBJECT  *NextDevice;  
  struct _DEVICE_OBJECT  *AttachedDevice;  
  struct _IRP  *CurrentIrp;  
  PIO_TIMER                   Timer;  
  ULONG                       Flags;  
  ULONG                       Characteristics;  
  __volatile PVPB             Vpb;  
  PVOID                       DeviceExtension;  
  DEVICE_TYPE                 DeviceType;  
  CCHAR                       StackSize;  
  union {  
    LIST_ENTRY         ListEntry;  
    WAIT_CONTEXT_BLOCK Wcb;  
  } Queue;  
  ULONG                       AlignmentRequirement;  
  KDEVICE_QUEUE               DeviceQueue;  
  KDPC                        Dpc;  
  ULONG                       ActiveThreadCount;  
  PSECURITY_DESCRIPTOR        SecurityDescriptor;  
  KEVENT                      DeviceLock;  
  USHORT                      SectorSize;  
  USHORT                      Spare1;  
  struct _DEVOBJ_EXTENSION  *  DeviceObjectExtension;  
  PVOID                       Reserved;  
} DEVICE_OBJECT, *PDEVICE_OBJECT;  
这里只对几个比较重要的字段进行说明:
Ø  DriverObject:指向创建此设备对象的驱动程序对象。同属于一个驱动程序的设备对象指向的是同一个驱动对象。
Ø  NextObject:指向同一个驱动程序创建的下一个设备对象。同一个驱动对象可以创建若干个设备对象,每个设备对象根据NextDevice来连成一个链表,最后一个设备对象的NextDevice域为NULL。
Ø  AttachedDevice:指向附加到此设备对象之上的最近设备对象。这里需要理解分层驱动程序的概念。
Ø  DeviceExtension:指向设备的扩展对象。每个设备都会指定一个设备扩展对象,这个数据结构由驱动程序开发者自行定义,可以用来记录一些与设备相关的一些信息,同时应尽量避免使用全局变量,将数据存放在设备扩展里,具有很大的灵活性。
Ø  CurrentIrp:在使用StartIO例程的时候,该成员指向的是当前IRP结构。
Ø  Flags:指定了该设备对象的标记。下面列出了常用的几个标记:
名称:  2.png查看次数: 1文件大小:  10.3 KB
Ø  DeviceType:指定设备的类型。一般在开发虚拟设备时,选择FILE_DEVICE_UNKNOW。其他的请自行参考WDK文档。
Ø  StackSize:在多层驱动情况下,驱动与驱动之间会形成类似堆栈的结构,称之为设备栈。IRP会依次从最高层传递到最底层。StackSize描述的就是该层数。最底层的设备层数为1。
Ø  AlignmentRequirement:在进行大容量传输的时候,往往需要进行内存对齐,以保证传输速度。请使用类似FILE_XXX_ALIGNMENT的方式进行赋值。

下面给大家展示一下DriverEntry的最基本框架:
代码:
#ifdef __cplusplus
extern "C"
{
#endif

#include <NTDDK.h>

#ifdef __cplusplus
};
#endif

#define PAGECODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")


typedef struct _DEVICE_EXTENSION
{
  PDEVICE_OBJECT pDevice;
  UNICODE_STRING ustrDeviceName;
  UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION , *PDEVICE_EXTENSION;

//函数声明
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *DeviceObject, __in struct _IRP  *Irp);
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);

///
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
                PUNICODE_STRING pRegistryPath)
{
  NTSTATUS status;
  PDEVICE_EXTENSION pDevExt;
  PDEVICE_OBJECT pDevObj;
  KdPrint(("\n--------------------------------------!\n"));
  KdPrint(("Enter DriverEntry!\n"));
  //注册相关例程
  pDriverObject->DriverUnload = UnloadRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CREATE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_READ]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_WRITE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE]  =  DispatchRoutine;

  //初始化相关字符串
  UNICODE_STRING ustrDeviceName;  //设备名
  UNICODE_STRING ustrSymLinkName; //符号链接名

  RtlInitUnicodeString(&ustrDeviceName,L"\\Device\\MyDDKDevice1");
  RtlInitUnicodeString(&ustrSymLinkName,L"\\??\\MyDDKDriver1");

  //创建设备对象
  
  status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&ustrDeviceName,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevObj);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("Create Device Failure!\n"));
    return status;  
  }
  pDevObj->Flags |= DO_BUFFERED_IO;
  pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
  pDevExt->ustrDeviceName = ustrDeviceName;
  pDevExt->pDevice = pDevObj;

  //创建符号链接
  pDevExt->ustrSymLinkName = ustrSymLinkName;
  status = IoCreateSymbolicLink(&ustrSymLinkName,&ustrDeviceName);
  if (!NT_SUCCESS(status))
  {
    IoDeleteDevice(pDevObj);
    return status;
  }
  KdPrint(("Leave DriverEntry! stauts=%d",status));
  return status;
}

这是用C++写的,所以必要的地方加上了extern“C”,否则会引起一些错误,这是因为C++与C在进行名称粉碎的时候处理得不一样,C++这个改进主要是为了实现一些高级功能,比如多态。虽然加上extern “C”会有点麻烦,但可以用上C++那些功能,个人觉得也有所值。如果用C,直接忽略上面的extern “C”。
 
NTDDK.h是NT式驱动需要加载的头文件,如果是WDM式驱动,那么加载的是WDM.h
 
#define INITCODE code_seg("INIT")定义一个宏,#prama INITCODE还原后就是#pramacode_seg(“INIT”),表示接下来的代码加载到INIT内存区域中,成功加载后,可以退出内存。对于DriverEntry这种一次性的函数而言,这是最适合的选择,可以节省内存。函数结束后需要显式地切换回来,如:#prama LOCKEDCODE。

同样,PAGECODE表示分页内存,作用是将此部分代码放入分页内存中运行,在里面的代码切换进程上下文时可能会被换回分页文件。LOCKEDCODE表示默认内存,也就是非分页内存,里面的代码常驻内存。IRQL处于DISPATCH_LEVEL或者以上的等级,必须处于非分页内存里面。

同理,对于数据段也有同样的机制,于是有了PAGEDATA、LOCKEDDATA、INITDATA。
 
KdPrint是一个宏,在调试版本(Checked)里面(具备DBG宏定义),有
代码:
#define KdPrint(_x_) DbgPrint _x_  

而在正式版本(Free)里面,KdPrint被定义为空。所以可以用来作为调试输出。但注意Kdprint后面是两层括号,用法与C语言运行库的printf差不多。

pDriverObject->DriverUnload = UnloadRoutine;将卸载例程函数告诉驱动对象,驱动对象在前面已经有定义,这里不做深入讨论。

pDriverObject->MajorFunction[IRP_MJ_CREATE]  =       DispatchRoutine;注册排遣例程。Windows是消息驱动,而驱动程序是IRP驱动的,I/O管理器将发送到驱动的“消息”封装在IRP里面,驱动程序也将结果告诉IRP。类似windows的消息机制,对于不同的“消息”,驱动程序需要注册不同的处理例程来区别对待,当然也可以放在同一个例程里面,然后用switch语句来区别对待,但当处理过程比较长的时候,会比较凌乱。

IRP_MJ_CREATE是当RING3应用程序在使用CreateFile函数建立与驱动程序的通信通道时所激活的。IRP_MJ_READ是ReadFile,IRP_MJ_WRITE是WriteFile,而IRP_MJ_CLOSE是CloseHandle关闭文件句柄的时候产生的。

小知识:对于WDM式驱动,仍需要注册AddDevice例程,pDriverObject->DriverExtension->AddDevice = WDMAddDeviceRoutine,设备对象的初始化将在AddDevice里面进行而不是DriverEntry。另外还需要注册IRP_MJ_PNP排遣函数。

前面有讲到UNICODE_STRING结构,那么这里就可以很好的了解初始化的这两个结构了,忘记的看回前面的,这里只列出RtlInitUnicodeString函数定义。

代码:
VOID RtlInitUnicodeString(PUNICODE_STRING  DestinationString,PCWSTR  SourceString);  
IoCreateDevice是注册设备对象,一个驱动必须对应这一个或多个设备对象。

代码:
NTSTATUS   
  IoCreateDevice(  
    IN PDRIVER_OBJECT  DriverObject,  
    IN ULONG  DeviceExtensionSize,  
    IN PUNICODE_STRING  DeviceName  OPTIONAL,  
    IN DEVICE_TYPE  DeviceType,  
    IN ULONG  DeviceCharacteristics,  
    IN BOOLEAN  Exclusive,  
    OUT PDEVICE_OBJECT  *DeviceObject  
    );  
Ø  DriverObject:驱动对象的指针,这里用的是入口函数传进来的驱动对象。每个驱动有若干个设备对象,每个设备对象只有一个驱动函数。
Ø  DeviceExtensionSize:自定义的设备扩展的大小。
Ø  DeviceName:设备对象名称,前面有用RtlInitUnicodeString对其进行过初始化。设备对象是暴露在内核层面上的名称,对于RING3层桌面程序是不可见的。格式需为:\Device\[设备名]。如果不指定设备名,I/O管理器将会自动分配一个数字作为设备名,如:\Device\00000001
Ø  DeviceType:设备类型,这里用FILE_DEVICE_UNKNOWN。
Ø  DeviceCharacteristics:设备对象的特征。
Ø  Exclusive:设置设备对象是否为专用的。也即是否允许有第二个驱动、程序访问这个设备对象。
Ø  DeviceObject:I/O管理器将会负责创建这个设备对象,可以用这个参数来接收该对象的地址。
创建了设备对象,在程序临近结束之际需要用IoDeleteDevice删除设备对象。
 
pDevObj->Flags |= DO_BUFFERED_IO;是设置访问标志为缓冲模式。
 
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;获取设备扩展。
 
pDevExt->ustrDeviceName = ustrDeviceName;将相关信息保存在设备扩展里面。
 
设备对象名称只暴露在内核层面,想要在RING3用户层访问驱动程序则需要创建符号链接。符号链接是暴露在用户层面的。注册符号链接用的函数是IoCreateSymbolicLink。

代码:
NTSTATUS 
  IoCreateSymbolicLink(
    IN PUNICODE_STRING  SymbolicLinkName,
    IN PUNICODE_STRING  DeviceName
    );
Ø  SymbolicLinkName:符号链接的字符串,前面有对其初始化。符号链接名需要以\??\开头,或者\DosDevice\(未证实)。而在用户模式下需要以\\.\开头才能找到对应的符号链接。
Ø  DeviceName:设备名的字符串。
注意:创建了符号链接,在程序临近结束之际需要用IoDeleteSymbolicLink删除符号链接。并且先删除符号链接在删除设备对象。
 
在上面的DriverEntry函数里面,已经完成了基本的初始化工作,接下来,我们看一下卸载回调例程。
 
回调例程的声明为:
代码:
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);  
除了函数名字外,请不要改动其他参数。
 
在回调函数里面,我们主要进行一些清理工作。
代码:
#pragma PAGECODE
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *pDriverObject)
{
  PDEVICE_OBJECT pNextObj;
  PDEVICE_EXTENSION pDevExt;
  pNextObj = pDriverObject->DeviceObject;
  KdPrint(("Enter unload routine!\n"));
  while(pNextObj != NULL)
  {
    pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;

    //删除符号链接
    IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
    pNextObj = pNextObj->NextDevice;
    //删除设备
    IoDeleteDevice(pDevExt->pDevice);
  }
  KdPrint(("Leave unload routine!\n"));
  KdPrint(("--------------------------------------!\n"));
}
基本在上面都已经有所描述了。这里还强调一点,一个驱动程序可以有一个或者多个设备对象,在驱动程序完全卸载之前需要删除对应的符号链接、设备对象。所以这里用到了while循环来完成这项工作,不明白的回去上面继续熟悉下设备对象的结构。
 
这个驱动程序没有做什么工作,所以在排遣函数里面我们只是单纯的设置状态为成功,操作的字节为0,设置IRP状态为完成,就返回了。

代码:
#pragma PAGECODE
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *pDeviceObject,
             __in struct _IRP  *pIrp)
{
  KdPrint(("Enter dispatch routine!\n"));
  NTSTATUS status = STATUS_SUCCESS;
  //完成IRP
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = 0;
  IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  KdPrint(("Leave dispatch routine!\n"));
  return status;
}
pIrp->IoStatus.Status = status;设置IO状态。

pIrp->IoStatus.Information = 0;设置实际操作的字节数。用户层函数ReadFile、WriteFile的第四个参数lpNumberOfBytesRead用于接收实际操作的字节数,这个结果就是这样产生的。

IoCompleteRequest设置完成IRP的处理,否则会继续往下层传递。
 
整一个框架都已基本介绍完毕了,下面贴上完整的代码吧。
代码:
#ifdef __cplusplus
extern "C"
{
#endif

#include <NTDDK.h>

#ifdef __cplusplus
};
#endif

#define PAGECODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

#define arrarysize(arr) (sizeof(arr)/sizeof(arr)[0])

typedef struct _DEVICE_EXTENSION
{
  PDEVICE_OBJECT pDevice;
  UNICODE_STRING ustrDeviceName;
  UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION , *PDEVICE_EXTENSION;


//函数声明
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *DeviceObject, __in struct _IRP  *Irp);
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *DriverObject);
    

///
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
                PUNICODE_STRING pRegistryPath)
{
  NTSTATUS status;
  PDEVICE_EXTENSION pDevExt;
  PDEVICE_OBJECT pDevObj;
  KdPrint(("\n--------------------------------------!\n"));
  KdPrint(("pRegistryPath value:%ws",pRegistryPath));
  KdPrint(("Enter DriverEntry!\n"));
  //注册相关例程
  pDriverObject->DriverUnload = UnloadRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CREATE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_READ]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_WRITE]  =  DispatchRoutine;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE]  =  DispatchRoutine;

  //初始化相关字符串
  UNICODE_STRING ustrDeviceName;  //设备名
  UNICODE_STRING ustrSymLinkName; //符号链接名

  RtlInitUnicodeString(&ustrDeviceName,L"\\Device\\MyDDKDevice1");
  RtlInitUnicodeString(&ustrSymLinkName,L"\\??\\MyDDKDriver1");

  //创建设备对象
  
  status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&ustrDeviceName,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevObj);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("Create Device Failure!\n"));
    return status;  
  }
  pDevObj->Flags |= DO_BUFFERED_IO;
  pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
  pDevExt->ustrDeviceName = ustrDeviceName;
  pDevExt->pDevice = pDevObj;

  //创建符号链接
  pDevExt->ustrSymLinkName = ustrSymLinkName;
  status = IoCreateSymbolicLink(&ustrSymLinkName,&ustrDeviceName);
  if (!NT_SUCCESS(status))
  {
    IoDeleteDevice(pDevObj);
    return status;
  }
  KdPrint(("Leave DriverEntry! stauts=%d",status));
  return status;
}
#pragma PAGECODE
NTSTATUS DispatchRoutine(__in struct _DEVICE_OBJECT  *pDeviceObject,
             __in struct _IRP  *pIrp)
{
  KdPrint(("Enter dispatch routine!\n"));
  NTSTATUS status = STATUS_SUCCESS;
  //完成IRP
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = 0;
  IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  KdPrint(("Leave dispatch routine!\n"));
  return status;
  
}

#pragma PAGECODE
VOID UnloadRoutine(__in struct _DRIVER_OBJECT  *pDriverObject)
{
  PDEVICE_OBJECT pNextObj;
  PDEVICE_EXTENSION pDevExt;
  pNextObj = pDriverObject->DeviceObject;
  KdPrint(("Enter unload routine!\n"));
  while(pNextObj != NULL)
  {
    pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;

    //删除符号链接
    IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
    pNextObj = pNextObj->NextDevice;
    //删除设备
    IoDeleteDevice(pDevExt->pDevice);
  }
  KdPrint(("Leave unload routine!\n"));
  KdPrint(("--------------------------------------!\n"));
}
*转载请注明来自看雪论坛@PEdiy.com  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值