Windows驱动程序框架

DriverWizard使用教程。 阅读详情

在配置好想对应的开发环境后,我们就可以开发驱动程序了。注:下面的主要以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"));
}
手把手教你学PCIE(6.6)--pcie驱动程序框架的Windows 驱动程序框架 用于开发内核模式驱动程序,适用于大多数设备驱动。:用于开发用户模式驱动程序,适用于不需要直接访问硬件的设备。PCIe 驱动程序在 Windows 内核中的开发框架提供了丰富的功能和抽象,使得开发 PCIe 驱动程序变得更加容易。通过定义模块初始化和清理函数、设备枚举和创建函数、设备初始化和配置函数、数据传输和中断处理函数、电源管理回调函数以及 I/O 请求处理函数,可以实现设备的全面管理和控制。以上步骤和示例代码提供了一个基本的框架,实际开发中可能需要根据具体的硬件和需求进行调整和优化。 阅读详情

相关推荐

windows驱动程序wdf--KMDF大致框架

继WDM后微软出了WDF,封装了WDM中的一些基本代码逻辑。本人菜鸟,也不知道本质上有何区别,只觉得是多了Wdf开头的函数,基本的编程框架上有点出入。KMDF是WDF的内核级部分,为了理清KMDF的结构,又觉得内核编程很复杂,HelloWorld类型的程序实在说明不了什么  修改一下《windows设备驱动WDF开发》的CharSample,查了WDK帮助文档加上注释以帮助自己理解KMDF的大致运

技术联盟 6288

win7,win10下删除HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\Root\报错

在调试虚拟网卡驱动时,由于修改错误,导致枚举顺序错乱,因此通过删除HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\Root\WINTUN下的所有项,即可,win10可用。但是,这个顺序和数目不是一成不变的,改变就发生在使用过“最近一次的正确配置”之后。“Default”数据项目表示 Windows 在下次启动时将使用的控件组,它与这次启动使用的控件组相同。“Default”数据项目表示 Windows 在下次启动时将使用的控件组,它与这次启动使用的控件组相同。

babytiger的专栏 3291

Sentence-BERT 语义相似度双塔模型

论文介绍 发表:2019,EMNLP 论文题目:《Sentence-BERT:sentence embeddings using siaese BERT-networks》 论文地址:https://arxiv.org/abs/1908.10084 Github:https://github.com/UKPLab/sentence-transformers 适用领域: 句向量生成 语义相似度计算 语义搜索 无监督任务(聚类) 1 概述 在20年之前的各种预训练模型中,比如bert,XLNet,Alb

weiyaner的博客 9195

Windows 操作系统架构介绍

Windows 系统架构包括以下组成部分:内核:Windows 内核是操作系统的核心部分,它负责管理系统资源、处理用户程序和驱动程序的请求、协调各种系统组件之间的通信等任务。Windows 内核分为用户模式和内核模式,其中内核模式是更高级别、更安全的模式,用户程序无法直接访问内核模式。驱动程序:Windows 系统需要很多不同类型的驱动程序来管理硬件设备和提供系统功能,如网络驱动程序、声卡驱动程序、显卡驱动程序等。这些驱动程序运行在内核模式下,可以访问系统的底层资源和硬件设备。

跑不了的你的博客 1万+

CCS TMSC6748 报错Error connecting to the target: (Error -600 @ 0x0)

CCS TMSC6748 报错Error connecting to the target:(Error -600@ 0x0)

AS_the_deer1717的博客 6637

地震勘探原理---地震预处理

3.1 为什么说要对数据进行振幅补偿?具体来说,地震波在传播过程中会遇到各种各样的地质条件,例如不同的岩层、土壤、水层等,这些介质的密度、速度等特性不同,因而对地震波的能量消耗也有所不同。在传播距离较远的情况下,能量损失可能会导致接收到的地震信号振幅变小。相反,在传播距离较近的情况下,能量损失可能较小,导致接收到的地震信号振幅变大。如果不对数据进行振幅补偿,这种振幅不均衡会导致地震数据分析和解释的误差和不确定性增加,可能产生错误的结论。

DARRENANJIAN的博客 3275

开源Windows驱动程序框架

转自 [url]http://code.youkuaiyun.com/news/2824273[/url] 源码地址 [url]https://github.com/Microsoft/Windows-driver-frameworks[/url] 微软宣布在 MIT 许可证下开源 Windows 驱动程序框架,源代码托管在 GitHub上。微软最新的开源举动可能有助于开源项目 Wine ...

Jacky_Dai的专栏 489

44、单级静态电流消耗最小化方法及功率优化比较器设计

本文探讨了单级静态电流消耗最小化方法及功率优化的多米诺逻辑比较器设计。通过分析类“AB”输入CMOS差分对电路,提出利用补偿电流源实现高低电流模式切换,提升运算放大器性能;结合VTCMOS与时钟门控技术,在90nm CMOS中设计低功耗比较器,并采用多米诺逻辑提高速度与能效。通过对比传统与改进型多米诺逻辑的静态功耗、动态功耗、延迟和PDP,验证了所提方案在高性能与低功耗方面的优势,适用于便携式设备等对能效要求严苛的应用场景。

pp12345的博客 122

微软开源驱动程序模块框架,编写 Windows 驱动程序更方便

百度智能云 云生态狂欢季 热门云产品1折起>>> 为了方便开发人员为 Windows 编写驱动程序,...

weixin_33858485的博客 206

cesium加载geotiff的 四种方法

本文介绍了在Cesium中加载GeoTIFF数据的两种主要方法。首先解析了核心前置知识,包括坐标系匹配、数据类型区分和性能优化。方法1详细演示了如何将RGB影像GeoTIFF转换为Cesium影像图层,包含从数据解析到Canvas转换的完整代码流程。方法2则针对高程数据(DEM)处理,展示了前端直接解析构建地形网格的方案。文章提供了完整的代码示例和关键实现要点,适用于卫星影像、专题图和数字高程模型等多种GeoTIFF数据类型在三维场景中的可视化需求。

大剑师兰特的GIS世界 396

Python 爬虫实战:华尔街见闻精选文章爬取(反反爬 + 投资情绪分析)

Python 爬虫实战:华尔街见闻精选文章爬取(反反爬 + 投资情绪分析)

yansideyucsdn的博客 970

微软开源Windows驱动程序框架

WDF 团队的代表 J M Rossy 在博客里是这样讲述开源 WDF 的主要目标。 昨天我们在 WinHEC 很激动的对外宣布将 Windows Driver Frameworks (WDF) 的源代码最大化的开放在 GitHub 上。相关的报告文件可以在这里看到:github.com/Microsoft/Windows-Driver-Frameworks WDF 开源的主要目标 开源 W...

weixin_30563917的博客 238

OpenCloudOS9操作系统搭建Confluence8.2.1+Jira企业级WIKI

Atlassian Confluence是一个专业的企业知识管理与协同软件,可用于构建企业wiki

背锅神童的博客 1023

【亲测免费】【免费下载】 探索WinFSP:一款强大的Windows文件系统驱动程序开发框架

是一个开源项目,由 Bill Zissimos 创建,旨在为Windows操作系统提供一个文件系统编程接口(API)。开发者可以利用它创建自己的虚拟文件系统,实现对数据的各种创新存储和访问方式。通过WinFSP,你可以构建像NTFS、FAT那样的实际文件系统,或者设计出全新的、符合特定需求的文件系统。 ## 技术分析 WinFSP(Windows File System Proxy)基于Mic...

gitblog_00013的博客 1万+

2026年数学建模国赛C题利器:层次分析法与主成分分析在统计建模中的应用

本文系统探讨了层次分析法(AHP)与主成分分析(PCA)在数学建模竞赛,特别是全国大学生数学建模竞赛C题中的联合应用策略。通过理论阐述、方法对比和案例实践,本文揭示了两种方法从“数据降维”到“决策评价”的完整分析链条,提出了“PCA+AHP”协同框架,并通过实际算例验证了该框架在解决复杂评价类问题中的有效性和优越性。研究表明,将PCA的客观降维能力与AHP的主观决策优势有机结合,能够显著提升统计建模的科学性、合理性和解释力,是应对日益复杂的竞赛题目的重要方法论工具。关键词:层次分析法;主成分分析;数学建模。

YOLO 41

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

python驱动程序开发课程_Windows WDF驱动程序开发视频课程(**框架)

如所周知,自Windows 2000开始,开发驱动程序以WDM为基础的,但其开发难度之大,根本不能奢望像用户模式应用程序开发那样容易。为改善这种局面,微软推出了新的驱动程序开发环境。要预先指出的是,这不是另起炉灶改弦更张,而是以WDM为基础进行了建模和封装,显著特点是降低了开发难度1、将原来普通程序设计中基于对象的技术应用到了驱动开发中。WDM中虽也有对象模型,但与真正的基于对象技术根本就不是一回...

weixin_39968946的博客 294

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

探索G4rb3n的Windows Driver:一个开源驱动程序开发框架

探索G4rb3n的Windows Driver:一个开源驱动程序开发框架 在软件开发的世界里,驱动程序扮演着连接硬件和操作系统的关键角色。G4rb3n的Windows Driver项目正是这样一个开源的、面向现代Windows操作系统的驱动程序开发框架,它旨在简化驱动程序开发流程,让开发者能够更高效地构建自己的驱动解决方案。 项目简介 Windows Driver是一个基于C++的框架,它提供了丰...

gitblog_00079的博客 372

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

使用WDF开发驱动程序

使用WDF开发驱动程序 Windows驱动程序框架(WDF)驱动程序由基于框架的驱动程序所使用的Windows驱动程序框架对象定义的DriverEntry 例程和一组事件回调函数组成。 创建WDF驱动程序时,执行操作: 使用框架驱动程序对象来表示驱动程序 驱动程序的DriverEntry 例程必须调用WdfDriverCreate以创建表示驱动程序的框架驱动程序对象。 WdfDriverCreate方法还会注册驱动程序的EvtDriverDeviceadd回调函数即插即用,该函数(PnP)管理 ...

Solong 1153

Windows驱动程序开发学习资料:项目推荐文章

Windows驱动程序开发学习资料:项目推荐文章 去发现同类优质开源项目:https://gitcode.com/ Windows驱动程序开发学习资料的核心功能/场景 掌握Windows驱动程序开发要点,提升系统级编程能力 项目介绍 在当今的软件开发领域,Windows驱动程序开发是一个极为重要的分支。它不仅涉及到操作系统的底层运作,更是系统级软件不可或缺的组成部分。今天,我们要推荐的这个开源项目...

gitblog_06783的博客 381
上一篇: VC6.0 + WDK 7600.16385.1 开发环境配置
下一篇: CRC原理及实现
epluguo
博客等级 码龄18年 34粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值