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的编码意义


其中

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:指定了该设备对象的标记。下面列出了常用的几个标记:

flag值

含义

DO_BUFFERED_IO

读写操作使用缓冲方式(系统复制缓冲区)访问用户模式数据

DO_EXCLUSIVE

一次只允许一个线程打开设备句柄

DO_DIRECT_IO

读写操作使用直接方式(内存描述符表)访问用户模式数据

DO_DEVICE_INITIALIZING

设备对象正在初始化

DO_POWER_PAGABLE

必须在PASSIVE_LEVEL级上处理IRP_MJ_PNP请求

Ø  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是一个宏,在调试版本里面(具备DBG宏定义),有

#define KdPrint(_x_) DbgPrint _x_

而在正式版本里面,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"));
}

转载于:https://my.oschina.net/iwuyang/blog/198615

猜你喜欢

转载自blog.csdn.net/weixin_33994444/article/details/91897352