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"));
}
相關文章
- 新字元驅動框架驅動LED字元框架
- 字元驅動框架字元框架
- SPI驅動框架一框架
- 如何將WHQL驅動程式釋出到 Windows 更新Windows
- windows驅動註冊中斷服務程式Windows
- Windows XP 中查詢驅動程式資訊(轉)Windows
- windows載入驅動Windows
- 字元裝置驅動 —— 字元裝置驅動框架字元框架
- DeviceDriver Windows NT 驅動程式型別 (轉載) (轉)devWindows型別
- Windows 11重新安裝音訊驅動程式的教程Windows音訊
- 使用Windows 95虛擬裝置驅動程式(VxD) (轉)Windows
- 分享一個LCD驅動框架框架
- 微軟更新 Windows 驅動安全指南微軟Windows
- windows驅動載入順序Windows
- Windows的驅動開發模型Windows模型
- Windows驅動開發入門Windows
- Windows核心驅動-程序回撥Windows
- Windows10系統載入ahci驅動程式的方法Windows
- Windows95的裝置驅動程式的編寫 (轉)Windows
- Windows NT 裝置驅動程式開發基礎(1) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(3) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(2) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(4) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(5) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(7) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(6) (轉)Windows
- Windows NT 裝置驅動程式開發基礎(8) (轉)Windows
- 【linux】驅動-5-驅動框架分層分離&實戰Linux框架
- 【系統安全003】NT驅動框架框架
- ESP32-LVGL驅動框架框架
- [Windows驅動開發](一)VS2008搭建windows驅動編譯環境Windows編譯
- Windows核心驅動學習(六)程式碼注入與核心掛鉤Windows
- 再說驅動程式
- [Microsoft][ODBC 驅動程式管理器] 驅動程式不支援此功能ROS
- Windows 7 安裝 N卡驅動,出現錯誤程式碼 52Windows
- iOS 裝置驅動 for windows 32&64iOSWindows
- Windows裝置和驅動的安裝Windows
- windows10驅動未通過怎麼辦_windows10檢測到驅動未通過處理方法Windows