NTDDK 從兩個最簡單的驅動談起


 

1

章 從兩個最簡單的驅動談起

Windows 驅動程序的編寫,往往需要開發人員對 Windows 內核有深入了解和大量的內

核調試技巧,稍有不慎,就會造成系統的崩潰。因此,初次涉及 Windows 驅動程序開發的

程序員,即使擁有大量 Win32 程序的開發技巧,往往也很難入門。

本章向讀者呈現兩個最簡單的 Windows 驅動程序,一個是 NT 式的驅動程序,另一個

是 WDM 式的驅動程序。這兩個驅動程序沒有操作具體的硬件設備,只是在系統里創建了

虛擬設備。在隨后的章節中,它們會作為基本驅動程序框架,被本書其他章節的驅動程序

開發所復用。筆者將帶領讀者編寫代碼、編譯、安裝和調試程序。相信對第一次編寫驅動

程序的讀者來說,這將是非常激動和有趣的。代碼的具體講解將分散在后面的章節論述。

現在請和筆者一起,開始 Windows 驅動編程之旅吧!

1.1 DDK的安裝

在編寫第一個驅動之前,需要先安裝微軟公司提供的 Windows 驅動程序開發包 DDK

(Driver Development Kit)。筆者計算機里安裝的是 Windows XP 2462 版本的 DDK,建議讀

者安裝同樣版本或者更高版本的 DDK,如圖 1-1 所示。

在安裝的時候請選擇完全安裝,即安裝 DDK 的所有部件,如圖 1-2 所示。因為除了

DDK 的基本編譯環境外,DDK 還提供了大量的源代碼和實用工具,這對於 Windows 驅動

程序的初學者進行學習和編寫驅動程序將是非常有用的。

安裝完畢后,會在開始菜單中出現相應的項目。其中,主要用到的是 Build Environment,

如圖 1-3 所示。該版本的 DDK 會同時安裝上 Windows 2000 和 Windows XP 的編譯環境。

 

第 1章 從兩個最簡單的驅動談起

圖 1-1 DDK 的安裝

圖 1-2 DDK 的安裝

圖 1-3 DDK 的編譯環境

1.2 第一個驅動程序 HelloDDK的代碼分析

Windows 驅動程序分為兩類,一類是不支持即插即用功能的 NT 式驅動程序,另一類

3

 

Windows驅動開發技術詳解

是支持即插即用功能的 WDM 驅動程序。本節介紹的 HelloDDK 是一個最簡單的 NT 式驅

動程序。在 1.4 節中將給出一個 WDM 式的驅動程序。

1.2.1 HelloDDK 的頭文件

HelloDDK 的頭文件主要是為了導入驅動程序開發所必需的 NTDDK.h 頭文件,此頭

文件里包含了對 DDK 的所有導出函數的聲明。NT 式的驅動程序要導入的頭文件是

NTDDK.h,而 WDM 式的驅動程序要導入的頭文件為 WDM.h。另外,此頭文件中定義了

幾個標簽,分別在程序中指明函數和變量分配在分頁內存中或非分頁內存中(分頁和非分

頁內存的概念將在第 3 章中講述)。最后,該頭文件給出了此驅動的函數聲明。

#001 /************************************************************************

#002 * 文件名稱:Driver.h

#003 * 作

者:張帆

#004 * 完成日期:2007-11-1

#005 *************************************************************************/

#006 #pragma once

#007

#008 #ifdef __cplusplus

#009 extern "C"

#010 {

#011 #endif

#012 #include <NTDDK.h>

#013 #ifdef __cplusplus

#014 }

#015 #endif

#016

#017 #define PAGEDCODE code_seg("PAGE")

#018 #define LOCKEDCODE code_seg()

#019 #define INITCODE code_seg("INIT")

#020

#021 #define PAGEDDATA data_seg("PAGE")

#022 #define LOCKEDDATA data_seg()

#023 #define INITDATA data_seg("INIT")

#024

#025 #define arraysize(p) (sizeof(p)/sizeof((p)[0]))

#026

#027 typedef struct _DEVICE_EXTENSION {

#028

PDEVICE_OBJECT pDevice;

#029

UNICODE_STRING ustrDeviceName;

//設備名稱

#030

UNICODE_STRING ustrSymLinkName;

//符號鏈接名

#031 } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

#032

#033 // 函數聲明

#034

#035 NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);

#036 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);

#037 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,

#038

#039

IN PIRP pIrp);

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

4

 

第 1 章 從兩個最簡單的驅動談起

代碼 6~15 行,包含 ddk.h 頭文件,所有的 NT 式驅動程序都要包含此頭文件。因

為這里采用的是 C++語言編寫,如果直接包含 ntddk.h,函數的符號表會導入錯誤,

所以需要加入 extern "C",這樣可以保證符號表正確導入。關於 C++編寫驅動需要

注意的地方,將在第 3 章進行論述。

代碼 17~23 行,定義分頁標記、非分頁標記和初始化內存塊。在 Windows 驅動程

序的開發中,所有程序的函數和變量要被指明被加載到分頁內存中還是在非分頁

內存中。程序代碼中加入這里定義的宏,就會被指明函數和變量是位於分頁或非

分頁內存中。另外,有一個特殊的函數 DriverEntry 需要放在 INIT 標志的內存中。

INIT 標志指明該函數只是在加載的時候需要載入內存,而當驅動程序成功加載后,

該函數可以從內存中卸載掉。

代碼 27~31 行,指定一個設備擴展結構體,這種結構體廣泛應用於驅動程序中。

根據不同驅動程序的需要,它負責補充定義設備的相關信息。

代碼 33~38 行是函數的聲明。

1.2.2 HelloDDK 的入口函數

和普通的應用程序不同,Windows 驅動程序的入口函數不是 main 函數,而是一個叫

做 DriverEntry 的函數,代碼將在下面列出。DriverEntry 函數由內核中的 I/O 管理器負責調

用,其函數有兩個參數:pDriverObject 和 pRegistryPath。其中,pDriverObject 是 I/O 管理

器傳遞進來的驅動對象,pRegistryPath 是一個 Unicode 字符串,指向此驅動負責的注冊表。

#001 /************************************************************************

#002 * 文件名稱:Driver.cpp

#003 * 作

者:張帆

#004 * 完成日期:2007-11-1

#005 *************************************************************************/

#006

#007 #include "Driver.h"

#008

#009 /************************************************************************

#010 * 函數名稱:DriverEntry

#011 * 功能描述:初始化驅動程序,定位和申請硬件資源,創建內核對象

#012 * 參數列表:

#013

#014

pDriverObject:從 I/O管理器中傳進來的驅動對象

pRegistryPath:驅動程序在注冊表的中的路徑

#015 * 返回值:返回初始化驅動狀態

#016 *************************************************************************/

#017 #pragma INITCODE

#018 extern "C" NTSTATUS DriverEntry (

#019

#020

IN PDRIVER_OBJECT pDriverObject,

IN PUNICODE_STRING pRegistryPath

)

#021 {

#022

NTSTATUS status;

5

 

Windows驅動開發技術詳解

#023

#024

KdPrint(("Enter DriverEntry\n"));

#025

//注冊其他驅動調用函數入口

#026

#027

#028

#029

#030

#031

pDriverObject->DriverUnload = HelloDDKUnload;

pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;

pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;

pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;

pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

#032

//創建驅動設備對象

#033

status = CreateDevice(pDriverObject);

#034

#035

#036

KdPrint(("DriverEntry end\n"));

return status;

#037 }

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

代碼 17 行,用#pragma 指明此函數是加載到 INIT 內存區域中,即成功卸載后,可

以退出內存。

代碼 18 行,標志 DriverEntry 函數的開始。注意此處在函數體的前面用 extern "C"修

飾,這樣在編譯的時候會編譯成_DriverEntry@8 的符號。如果不加入此修飾符號,

編譯器會自動按照 C++的符號名編譯,導致錯誤鏈接。

代碼 23 行,打印一行調試信息。KdPrint 其實是一個宏,在調試版本(Checked 版)

中,會用 DbgPrint 代替。而在發行版(Free 版)中,則不執行任何操作,其功能

類似於 MFC 中的 TRACE 宏。由於驅動程序是運行在 Windows 的核心態,沒有用

戶界面,所以查看調試信息有別於 Win32 程序。關於查看調試信息的講解將在第 3

章論述。

代碼 26~30 行,驅動程序向 Windows 的 I/O 管理器注冊一些回調函數。回調函數

是由程序員定義的函數,這些函數不是由驅動程序本身負責調用,而是由操作系

統負責調用。程序員將這些函數的入口地址告訴操作系統,操作系統會在適當的

時候調用這些函數。在這個例子中,這幾個回調函數基本是自解釋型的,讀者可

以根據函數名分析出其作用。當驅動被卸載時,調用 HelloDDKUnload。當驅動程

序處理創建、關閉和讀寫相關的 IRP 時,調用 HelloDDKDispatchRoutine(這里只

是將處理函數簡化為一個函數,實際情況要比這個復雜)。

代碼第 33 行,調用 CreateDevice 函數,此函數的解釋見下一節。

代碼第 36 行,返回 CreateDevice 的執行結果。如果執行正確,驅動將被成功加載。

1.2.3 創建設備例程

CreateDevice 函數是一個幫助函數(Helper Function),輔助 DriverEntry 創建一個設備

對象。其完全可以展開放在 DriverEntry 中,但為了代碼的條理性,筆者將其構造成一個輔

6

 

第 1 章 從兩個最簡單的驅動談起

助函數。

#001 /************************************************************************

#002 * 函數名稱:CreateDevice

#003 * 功能描述:初始化設備對象

#004 * 參數列表:

#005

pDriverObject:從 I/O管理器中傳進來的驅動對象

#006 * 返回 值:返回初始化狀態

#007 *************************************************************************/

#008 #pragma INITCODE

#009 NTSTATUS CreateDevice (

#010

IN PDRIVER_OBJECTpDriverObject)

#011 {

#012

NTSTATUS status;

#013

PDEVICE_OBJECT pDevObj;

#014

PDEVICE_EXTENSION pDevExt;

#015

#016

//創建設備名稱

#017

#018

#019

UNICODE_STRING devName;

RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");

#020

//創建設備

#021

#022

#023

#024

#025

#026

#027

#028

#029

#030

#031

#032

#033

status = IoCreateDevice( pDriverObject,

sizeof(DEVICE_EXTENSION),

&(UNICODE_STRING)devName,

FILE_DEVICE_UNKNOWN,

0, TRUE,

&pDevObj );

if (!NT_SUCCESS(status))

return status;

pDevObj->Flags |= DO_BUFFERED_IO;

pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

pDevExt->pDevice = pDevObj;

pDevExt->ustrDeviceName = devName;

#034

//創建符號鏈接

#035

#036

#037

#038

#039

#040

#041

#042

#043

#044

#045 }

UNICODE_STRING symLinkName;

RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");

pDevExt->ustrSymLinkName = symLinkName;

status = IoCreateSymbolicLink( &symLinkName,&devName );

if (!NT_SUCCESS(status))

{

IoDeleteDevice( pDevObj );

return status;

}

return STATUS_SUCCESS;

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

代碼 16~18 行,構造一個 Unicode 字符串,此字符串用來存儲此設備對象的名稱。

Unicode 字符串大量運用在驅動程序開發中,有關 Unicode 的講解請參考第 3 章。

代碼 21~28 行,用 IoCreateDevice 函數創建一個設備對象。其對象名稱來自於上一

步構造的 Unicode 字符串,設備類型為 FILE_DEVICE_UNKNOWN,且此種設備

7

 

Windows驅動開發技術詳解

為獨占設備,即設備只能被一個應用程序所使用。

代碼第 30 行,表明此種設備為 BUFFERED_IO 設備。設備對內存的操作分為兩種,

BUFFERED_IO 和 DO_DIRECT_IO,此部分講解請參考第 3 章。

代碼 31~33 行,填寫設備的擴展結構體,在其他驅動程序的函數中,可以很方

便地得到這個結構體,進而得到該設備的自定義信息。此結構體的定義在

Driver.h 中。

代碼 34~38 行,創建符號鏈接。驅動程序雖然有了設備名稱,但是這種設備名稱

只能在內核態可見,而對於應用程序是不可見的。因此,驅動需要暴露一個符號

鏈接,該鏈接指向真正的設備名稱。

代碼 39~44 行,當設備創建成功后返回。如果不成功,則刪除該設備。

1.2.4 卸載驅動例程

卸載驅動例程用來設備被卸載的情況,由 I/O 管理器負責調用此回調函數。此例程遍

歷系統中所有的此類設備對象。第一個設備對象的地址存在於驅動對象的 DeviceObject 域

中,每個設備對象的 NextDevice 域記錄着下一個設備對象的地址,這樣就形成一個鏈表。

卸載驅動例程的主要目的就是遍歷系統中所有的此類設備對象,然后刪除設備對象以及符

號鏈接。

#001 /************************************************************************

#002 * 函數名稱:HelloDDKUnload

#003 * 功能描述:負責驅動程序的卸載操作

#004 * 參數列表:

#005

pDriverObject:驅動對象

#006 * 返回值:返回狀態

#007 *************************************************************************/

#008 #pragma PAGEDCODE

#009 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)

#010 {

#011

#012

#013

#014

#015

#016

#017

#018

PDEVICE_OBJECT

pNextObj;

KdPrint(("Enter DriverUnload\n"));

pNextObj = pDriverObject->DeviceObject;

while (pNextObj != NULL)

{

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)

pNextObj->DeviceExtension;

#019

//刪除符號鏈接

#020

#021

#022

#023

UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;

IoDeleteSymbolicLink(&pLinkName);

pNextObj = pNextObj->NextDevice;

IoDeleteDevice( pDevExt->pDevice );

}

#024

#025 }

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

8

 

第 1章 從兩個最簡單的驅動談起

代碼第 13 行,由驅動對象得到設備對象。

代碼 19~21 行,刪除設備對象的符號鏈接。

代碼 22~23 行,遍歷設備對象,並刪除。

1.2.5 默認派遣例程

對設備對象的創建、關閉和讀寫操作,都被指定到這個默認的派遣例程中。由於這是

一個最簡單的演示程序,故只是簡單地將其成功返回。后面的章節中,筆者將會擴充該

例程。

#001 /************************************************************************

#002 * 函數名稱:HelloDDKDispatchRoutine

#003 * 功能描述:對讀 IRP進行處理

#004 * 參數列表:

#005

#006

pDevObj:功能設備對象

pIrp:從 I/O請求包

#007 * 返回值:返回狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,

#011

IN PIRP pIrp)

#012 {

#013

#014

KdPrint(("Enter HelloDDKDispatchRoutine\n"));

NTSTATUS status = STATUS_SUCCESS;

#015

// 完成 IRP

#016

pIrp->IoStatus.Status = status;

#017

#018

#019

#020

pIrp->IoStatus.Information = 0;

IoCompleteRequest( pIrp, IO_NO_INCREMENT );

KdPrint(("Leave HelloDDKDispatchRoutine\n"));

return status;

// bytes xfered

#021 }

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

代碼 16 行,設置 IRP 的狀態為成功。IRP 的講解請參考第 4 章。

代碼 17 行,設置操作的字節數為 0,這里無實際意義。

代碼 18 行,指示完成此 IRP。

代碼 20 行,成功返回。

1.3 HelloDDK的編譯和安裝

本節會帶領讀者一步步地對 HelloDDK 進行編譯和安裝。編譯和安裝往往是初學者最

先需要面對的問題,筆者將會從兩個方面講解編譯過程。一是傳統的用 DDK 編譯環境編

譯,二是用 Visual C++(以下簡稱 VC)集成開發環境編譯。

9

 

Windows驅動開發技術詳解

1.3.1 用 DDK 環境編譯 HelloDDK

這種編譯驅動的辦法是 DDK 文檔中所提倡的辦法。此種方法需要編寫一個編譯腳本

文件,在這個腳本中描述了 DDK 驅動程序的源文件、用到的 lib 文件和 inlcude 路徑名、編

譯輸出的目錄和文件名等信息,具體介紹請參考第 3 章。編寫此類腳本對於 Windows 程序

員可能比較陌生,尤其是當源文件較多時,編寫腳本文件可能顯得更如麻煩。下一節將向讀

者介紹一種簡單的用 Visual C++ 6.0 IDE(以下簡稱 VC IDE)環境編譯驅動的方法。在源程

序的相同目錄下創建兩個文件 makefile 和 Sources,這兩個文件都是文本文件,內容如下。

Sources:

#001 TARGETNAME=HelloDDK

#002 TARGETTYPE=DRIVER

#003 TARGETPATH=OBJ

#004

#005 INCLUDES=$(BASEDIR)\inc;\

#006

#007

$(BASEDIR)\inc\ddk;\

#008 SOURCES=Driver.cpp\

此段代碼可以在配套光盤中本章的 NT_Driver 目錄下找到。

第 1 行說明此驅動的名稱。

第 2 行指明此驅動的類型為 NT 型驅動。

第 3 行設置編譯輸出目錄。

第 5~6 行設置 include 目錄。

第 8 行指定源文件。

編寫完這兩個腳本后,在 Windows 的開始菜單中選擇“Windows XP Checked Build

Environment”編譯環境。這里選擇的是 Checked 版本,而不是 Free 版本。兩者的區

別類似於 Win32 程序開發的 Debug 版本和 Release 版本,具體的差別詳見第 3 章。

選擇版本后,進入的是一個命令行方式的窗口。用 cd 命令進入需要編譯的目錄,然

后輸入“build”DDK 的編譯環境會自動調用編譯器進行編譯,筆者計算機中的編譯結果

如圖 1-4 所示。

10

 

第 1 章 從兩個最簡單的驅動談起

圖 1-4 DDK 的編譯環境

編譯好的結果位於源碼目錄下的子目錄 objchk_wxp_x86\i386(如果讀者是用 Windows

2000 DDK 編譯的,目錄會稍有不同)中。編譯出來的二進制文件為 HelloDDK.sys,它不

像 exe 文件那樣運行,而是必須通過特殊的加載方式加載,詳見 1.3.3 節。

1.3.2 用 VC 集成開發環境編譯 HelloDDK

初次學習編寫 Windows 驅動程序的開發人員,大部分是熟悉 VC IDE 開發環境的

Windows 程序員。他們可能不喜歡用編輯腳本來描述一個工程,而是更希望在熟悉的 VC

IDE 環境下編譯,並且利用 VC IDE 可以方便快速地對代碼進行交叉索引等操作。本節將

向讀者介紹此種方法。

(1)用 VC 建立一個新工程。在 VC IDE 環境中選擇“File”|“New”,彈出“New”對

話框。在該對話框中,選擇“Project”選項卡。在“Project”選項卡中,選擇 Win32 Application

(因為 VC 並沒有提供驅動程序的工程,所以在 Win32 工程的基礎上進行修改)。工程名為

“DriverDev”,如圖 1-5 所示。單擊“OK”按鈕,進入下一個對話框。在該對話框中,選擇

一個空的工程,如圖 1-6 所示。

圖 1-5 添加新工程

11

 

Windows驅動開發技術詳解

圖 1-6 創建新工程

(2)將兩個源文件 Driver.h 和 Driver.cpp 拷貝到工程目錄中,並添加到工程中,如圖

1-7 所示。

圖 1-7 添加新文件到工程

(3)增加新的編譯版本,去掉 Debug 和 Release 版本,如圖 1-8 和圖 1-9 所示。

圖 1-8 配置編譯版本

圖 1-9 修改后的 check 版本

(4)修改工程屬性。選擇“Project”“| Setting”,或者直接按下 Alt+F7 鍵,彈出“Project

Settings”對話框。在對話框中,選擇“General”選項卡。將 Intermediate files 和 Output files

改為 MyDriver_Check,如圖 1-10 所示。

12

 

第 1 章 從兩個最簡單的驅動談起

圖 1-10 修改輸出目錄

選擇 C/C++選項卡,將原有的 Project Options 內容全部刪除,替換成如下內容,如圖

1-11 所示。

/nologo /Gz /MLd /W3 /WX /Z7 /Od /D WIN32=100 /D _X86_=1 /D WINVER=0x500 /D DBG=1

/Fo"MyDriver_Check/" /Fd"MyDriver_Check/" /FD /c

圖 1-11 修改 C++選項卡

選擇 Link 選項卡,將原有的 Project Options 內容全部刪除,替換成如下內容,如圖

1-12 所示。

ntoskrnl.lib /nologo /base:"0x10000" /stack:0x400000,0x1000 /entry:"DriverEntry"

/subsystem:console /incremental:no /pdb:"MyDriver_Check/HelloDDK.pdb" /debug

/machine:I386 /nodefaultlib /out:"MyDriver_Check/HelloDDK.sys" /pdbtype:sept

/subsystem:native /driver /SECTION:INIT,D /RELEASE /IGNORE:4078

13

 

Windows驅動開發技術詳解

圖 1-12 修改 link 選項卡

(5)修改 VC 的 lib 目錄和 include 目錄。在 VC 中選擇“Tools”|“Options”,在彈出的

對話框中選擇“Directories”選項卡。在“Show directories for”下拉菜單中選擇“Include files”

菜單。添加“D:\WINDDK\2600.1106\INC\W2K”和“D:\WINDDK\2600.1106\INC\DDK\W2K”,

並將這兩個目錄置於最上,如圖 1-13(a)所示。讀者可將“D:\WINDDK\ 2600.1106”替換

成自己的 DDK 安裝目錄。這里應該選擇 W2K 子目錄,DDK 中還會有相應的 XP 子目錄。

因為 XP 驅動編譯時候需要高版本的 VC 編譯器,所以這里用的是 W2K 子目錄,它編譯的

代碼完全可以應用於 Windows 2000 和 Windows XP 操作系統下。

(a)

14

 

第 1 章 從兩個最簡單的驅動談起

(b)

圖 1-13 設置 include 目錄和設置 lib 目錄

在“Show directories for”下拉菜單中選擇“Library files”菜單,添加目錄“D:\WINDDK\

2600.1106\LIB\W2K\I386”,並置於最上端,如圖 1-13(b)所示。

(6)編譯。按下 F7 鍵,和 1.3.2 節一樣,同樣會編譯出一個 HelloDDK.sys 文件。

1.3.3 HelloDDK 的安裝

NT 式驅動程序類似於 Windows 服務程序,以服務的方式加載在系統中。在第 3 章

中,筆者將給出加載驅動的代碼。為了簡化步驟,這里利用一個叫做 DriverMonitor 的

工具軟件加載 HelloDDK。DriverMonitor 是 Compuware 公司開發的 DriverStudio 中的一

個工具,筆者將在第 4 章對 DriverStudio 提供的一系列工具軟件逐一介紹。筆者強烈推

薦讀者安裝 DriverStudio,因為它提供的一系列工具對調試驅動非常有用。如果讀者沒

有安裝 DriverMonitor,請參閱第 3 章,筆者將介紹如何編寫一個 NT 式驅動程序的加

載器。

運行 DriverMonitor,選擇“File”|“Open Driver”,將會彈出文件選擇對話框,選擇

編譯好的 HelloDDK.sys 文件。再次選擇“File”|“Start Driver”。至此,NT 驅動加載成功,

DriverMonitor 會報告加載情況,如圖 1-14 所示。

15

 

Windows驅動開發技術詳解

圖 1-14 用 Driver Monitor 安裝 HelloDDK

用 Driver Monitor 加載驅動時,默認是加載一次。重新啟動電腦后,該驅動不會被加

載。如果想在每次開機啟動時自動加載,需要修改設置。選擇 “Edit”|“Properties”,彈

出如圖 1-15 所示的對話框。在“Start Type”選擇組中,選擇“Automatic”單選按鈕。保

存后,就可以在每次開機啟動時自動加載該驅動。

成功加載的驅動,會出現在 Windows 的設備管理器中。默認情況下,NT 式的驅動程

序是隱藏的,在設備管理器中選擇“查看”|“顯示隱藏設備”,如圖 1-16 所示。

在“Not-Plug and Play Drivers”的列表中,會出現 HelloDDK 的驅動程序,如圖 1-17

所示。

圖 1-16 選擇加載方式

圖 1-17 在設備管理器中顯示 HelloDDK 設備

16

 

第 1章 從兩個最簡單的驅動談起

圖 1-17 在設備管理器中顯示設備

1.4 第二個驅動程序 HelloWDM的代碼分析

本節編寫的 HelloWDM 是基於 WDM 的驅動程序,和前面介紹的 HelloDDK 非常相似,

且多了對即插即用功能的支持。NT 式驅動和 WDM 驅動的異同將在第 3 章介紹。

1.4.1 HelloWDM 的頭文件

HelloWDM 的頭文件主要是為了導入驅動程序開發所必需的 WDM.h 頭文件,此頭文

件里包含了對 DDK 所有導出函數的聲明。NT 式的驅動程序要導入的是 NTDDK.h,而

WDM 驅動要導入的頭文件為 WDM.h。另外,此頭文件中定義了幾個標簽,分別在程序中

指明函數和變量分配在分頁內存中或非分頁內存中。最后,該頭文件給出了此驅動程序的

函數聲明。

#001 /************************************************************************

#002 * 文件名稱:HelloWDM.h

#003 * 作

者:張帆

#004 * 完成日期:2007-11-1

#005 *************************************************************************/

#006

#007 #ifdef __cplusplus

#008

#009 extern "C"

17

 

Windows驅動開發技術詳解

#010 {

#011 #endif

#012 #include <wdm.h>

#013 #ifdef __cplusplus

#014 }

#015 #endif

#016

#017 typedef struct _DEVICE_EXTENSION

#018 {

#019

#020

PDEVICE_OBJECT fdo;

PDEVICE_OBJECT NextStackDevice;

#021

#022

UNICODE_STRING ustrDeviceName; // 設備名

UNICODE_STRING ustrSymLinkName;

// 符號鏈接名

#023 } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

#024

#025 #define PAGEDCODE code_seg("PAGE")

#026 #define LOCKEDCODE code_seg()

#027 #define INITCODE code_seg("INIT")

#028

#029 #define PAGEDDATA data_seg("PAGE")

#030 #define LOCKEDDATA data_seg()

#031 #define INITDATA data_seg("INIT")

#032

#033 #define arraysize(p) (sizeof(p)/sizeof((p)[0]))

#034

#035 NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,

#036

#037 NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,

#038 IN PIRP Irp);

#039 NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,

#040 IN PIRP Irp);

IN PDEVICE_OBJECT PhysicalDeviceObject);

#041 void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject);

#042

#043 extern "C"

#044 NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

#045

IN PUNICODE_STRING RegistryPath);

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄下找到。

代碼 7~15 行,包含頭文件 wdm.h。和 HelloDDK 的頭文件類似,在引用時也用 extern

"C"進行修飾,為的是保持符號鏈接的正確性。注意,此處包含的是 wdm.h 而非

ntddk.h,這 是 WDM 驅動程序和 NT 驅動程序最主要的區別。關於 WDM 驅動程序

和 NT 驅動程序的不同,請參考第 3 章。

代碼 17~23 行,定義了設備擴展結構體,此結構體為本設備記錄相關信息。

代碼 25~31 行,定義了分頁內存、非分頁內存和 INIT 段內存的標志,以便在下面

的程序中聲明。

代碼 35~45 行為代碼聲明。

1.4.2 HelloWDM 的入口函數

和 NT 式驅動程序一樣,WDM 的入口函數地址同樣是 DriverEntry,且在 C++編譯的

18

 

第 1 章 從兩個最簡單的驅動談起

時候需要用 extern "C"修飾。

#001 /************************************************************************

#002 * 函數名稱:DriverEntry

#003 * 功能描述:初始化驅動程序,定位和申請硬件資源,創建內核對象

#004 * 參數列表:

#005

#006

pDriverObject:從 I/O管理器中傳進來的驅動對象

pRegistryPath:驅動程序在注冊表的中的路徑

#007 * 返回值:返回初始化驅動狀態

#008 *************************************************************************/

#009 #pragma INITCODE

#010 extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,

#011

IN PUNICODE_STRING pRegistryPath)

#012 {

#013

KdPrint(("Enter DriverEntry\n"));

#014

#015

#016

#017

#018

pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;

pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;

pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =

pDriverObject->MajorFunction[IRP_MJ_CREATE] =

#019

pDriverObject->MajorFunction[IRP_MJ_READ] =

#020

#021

pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;

pDriverObject->DriverUnload = HelloWDMUnload;

#022

#023

#024

KdPrint(("Leave DriverEntry\n"));

return STATUS_SUCCESS;

#025 }

代碼第 9 行,將此函數放在 INIT 段中,當驅動加載結束后,此函數就可以從內存

中卸載掉。

代碼 10~11 行,定義驅動入口函數,用 extern "C"修飾。其中傳進來兩個參數,第

一個參數 pDriverObject 為驅動對象,第二個參數 pRegistryPath 為此驅動的注冊

表路徑。

代碼第 15 行,設置 AddDevice 回調函數,此回調函數只出現在 WDM 驅動程序中,

而在 NT 式的驅動中沒有此回調函數。此回調函數的作用是創建設備對象並由 PNP

(即插即用)管理器調用。

代碼 16 行,設置對 IRP_MJ_PNP 的 IRP 的回調函數。對 PNP 的 IRP 處理,是 NT

式驅動和 WDM 驅動的重大區別之一。

代碼 17~20 行,設置常用 IRP 的回調函數。這里只是簡單地指向了同一個默認函

數 HelloWDMDispatchRoutine。

代碼 21 行,向系統注冊卸載例程。在 WDM 程序中,大部分卸載工作已不在此處

理,而是放在對 IRP_MN_REMOVE_DEVICE 的 IRP 的處理函數中處理。

1.4.3 HelloWDM 的 AddDevice 例程

在 WDM 的驅動程序中,創建設備對象的任務不再由 DriverEntry 承擔,而需要驅動

19

 

Windows驅動開發技術詳解

程序向系統注冊一個稱做 AddDevice 的例程。此例程由 PNP 管理器負責調用,其函數主

要 職 責 是 創 建 設 備 對 象 。 HelloWDMAddDevice 例 程 有 兩 個 參 數 , DriverObject 和

PhysicalDeviceObject。DriverObject 是由 PNP 管理器傳遞進來的驅動對象,此對象是

DriverEntry 中的驅動對象。PhysicalDeviceObject 是 PNP 管理器傳遞進來的底層驅動設備

對象,這個概念在 NT 式的驅動中是沒有的。關於這個概念,請參考第 3 章。

#001 /************************************************************************

#002 * 函數名稱:HelloWDMAddDevice

#003 * 功能描述:添加新設備

#004 * 參數列表:

#005

#006

DriverObject:從 I/O管理器中傳進來的驅動對象

PhysicalDeviceObject:從 I/O管理器中傳進來的物理設備對象

#007 * 返回值:返回添加新設備狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,

#011

#012 {

#013

#014

#015

#016

#017

#018

#019

#020

#021

#022

#023

#024

#025

#026

#027

#028

#029

#030

#031

#032

IN PDEVICE_OBJECT PhysicalDeviceObject)

PAGED_CODE();

KdPrint(("Enter HelloWDMAddDevice\n"));

NTSTATUS status;

PDEVICE_OBJECT fdo;

UNICODE_STRING devName;

RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");

status = IoCreateDevice(

DriverObject,

sizeof(DEVICE_EXTENSION),

&(UNICODE_STRING)devName,

FILE_DEVICE_UNKNOWN,

0,

FALSE,

&fdo);

if( !NT_SUCCESS(status))

return status;

PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

pdx->fdo = fdo;

pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, Physical

DeviceObject);

#033

#034

#035

#036

#037

#038

UNICODE_STRING symLinkName;

RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");

pdx->ustrDeviceName = devName;

pdx->ustrSymLinkName = symLinkName;

status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_

STRING)devName);

#039

#040

#041

#042

#043

#044

#045

#046

if( !NT_SUCCESS(status))

{

IoDeleteSymbolicLink(&pdx->ustrSymLinkName);

status = IoCreateSymbolicLink(&symLinkName,&devName);

if( !NT_SUCCESS(status))

{

return status;

20

 

第 1 章 從兩個最簡單的驅動談起

#047

#048

#049

#050

#051

#052

#053

#054

#055 }

}

}

fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;

fdo->Flags &= ~DO_DEVICE_INITIALIZING;

KdPrint(("Leave HelloWDMAddDevice\n"));

return STATUS_SUCCESS;

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄中找到。

代碼第 9 行,用#pragma 指明將此例程分派在分頁內存中。

代碼第 13 行,PAGED_CODE 是一個 DDK 提供的宏,只在 check 版中有效。當此

例程所在的中斷請求級超過 APC_LEVEL 時,會產生一個斷言,斷言會使程序終

止,並報告出錯地址。有關中斷請求級的講解,請參考第 3 章。

代碼 16~27 行,創建設備對象,此處和 HelloDDK 的創建方法一樣。

代碼 30 行,得到設備對象擴展數據結構。

代碼 31 行,記錄設備擴展中的功能設備對象為其剛才創建的設備。

代碼 32 行,用 IoAttachDeviceToDeviceStack 函數將此 fdo(功能設備對象)掛接

在設備堆棧上,並將返回值(下層堆棧的位置),記錄在設備擴展結構中。

代碼 37~38 行,創建設備的符號鏈接,此處和 HelloDDK 方法一樣。

代碼 50~51 行,設置設備為 BUFFERED_IO 設備,並指明驅動初始化完成。

代碼 54 行,成功返回。

1.4.4 HelloWDM 處理 PNP 的回調函數

WDM 式驅動程序主要區別在於對 IRP_MJ_PNP 的 IRP 的處理。其中,IRP_MJ_PNP

會細分為若干個子類。例如,IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE、

IRP_MN_STOP_DEVICE 等。本例中除了對 IRP_MN_REMOVE_DEVICE 做特殊處理,其

他 IRP 則做相同處理。

#001 /************************************************************************

#002 * 函數名稱:HelloWDMPnp

#003 * 功能描述:對即插即用 IRP進行處理

#004 * 參數列表:

#005

#006

fdo:功能設備對象

Irp:從 I/O請求包

#007 * 返回值:返回狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,

#011

IN PIRP Irp)

#012 {

#013

PAGED_CODE();

#014

21

 

Windows驅動開發技術詳解

#015

#016

#017

#018

#019

#020

#021

#022

#023

#024

#025

#026

#027

#028

#029

#030

#031

#032

#033

#034

#035

#036

#037

#038

#039

#040

#041

#042

#043

#044

#045

#046

#047

#048

KdPrint(("Enter HelloWDMPnp\n"));

NTSTATUS status = STATUS_SUCCESS;

PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) =

{

DefaultPnpHandler,

DefaultPnpHandler,

HandleRemoveDevice,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

DefaultPnpHandler,

// IRP_MN_START_DEVICE

// IRP_MN_QUERY_REMOVE_DEVICE

// IRP_MN_REMOVE_DEVICE

// IRP_MN_CANCEL_REMOVE_DEVICE

// IRP_MN_STOP_DEVICE

// IRP_MN_QUERY_STOP_DEVICE

// IRP_MN_CANCEL_STOP_DEVICE

// IRP_MN_QUERY_DEVICE_RELATIONS

// IRP_MN_QUERY_INTERFACE

// IRP_MN_QUERY_CAPABILITIES

// IRP_MN_QUERY_RESOURCES

// IRP_MN_QUERY_RESOURCE_REQUIREMENTS

// IRP_MN_QUERY_DEVICE_TEXT

// IRP_MN_FILTER_RESOURCE_REQUIREMENTS

//

// IRP_MN_READ_CONFIG

// IRP_MN_WRITE_CONFIG

// IRP_MN_EJECT

// IRP_MN_SET_LOCK

// IRP_MN_QUERY_ID

// IRP_MN_QUERY_PNP_DEVICE_STATE

// IRP_MN_QUERY_BUS_INFORMATION

// IRP_MN_DEVICE_USAGE_NOTIFICATION

// IRP_MN_SURPRISE_REMOVAL

};

ULONG fcn = stack->MinorFunction;

if (fcn >= arraysize(fcntab))

#049

#050

{

//未知的 IRP類別

status = DefaultPnpHandler(pdx, Irp); // 對於未知的 IRP類別,我們讓

// DefaultPnpHandler函數處理

#051

#052

#053

return status;

}

//未知的 IRP類別

#054 #if DBG

#055

#056

#057

#058

#059

#060

#061

#062

#063

#064

#065

#066

#067

#068

#069

#070

#071

static char* fcnname[] =

{

"IRP_MN_START_DEVICE",

"IRP_MN_QUERY_REMOVE_DEVICE",

"IRP_MN_REMOVE_DEVICE",

"IRP_MN_CANCEL_REMOVE_DEVICE",

"IRP_MN_STOP_DEVICE",

"IRP_MN_QUERY_STOP_DEVICE",

"IRP_MN_CANCEL_STOP_DEVICE",

"IRP_MN_QUERY_DEVICE_RELATIONS",

"IRP_MN_QUERY_INTERFACE",

"IRP_MN_QUERY_CAPABILITIES",

"IRP_MN_QUERY_RESOURCES",

"IRP_MN_QUERY_RESOURCE_REQUIREMENTS",

"IRP_MN_QUERY_DEVICE_TEXT",

"IRP_MN_FILTER_RESOURCE_REQUIREMENTS",

"",

22

 

第 1 章 從兩個最簡單的驅動談起

#072

#073

#074

#075

#076

#077

#078

#079

#080

#081

#082

#083

"IRP_MN_READ_CONFIG",

"IRP_MN_WRITE_CONFIG",

"IRP_MN_EJECT",

"IRP_MN_SET_LOCK",

"IRP_MN_QUERY_ID",

"IRP_MN_QUERY_PNP_DEVICE_STATE",

"IRP_MN_QUERY_BUS_INFORMATION",

"IRP_MN_DEVICE_USAGE_NOTIFICATION",

"IRP_MN_SURPRISE_REMOVAL",

};

KdPrint(("PNP Request (%s)\n", fcnname[fcn]));

#084 #endif // DBG

#085

#086

status = (*fcntab[fcn])(pdx, Irp);

#087

#088

KdPrint(("Leave HelloWDMPnp\n"));

return status;

#089 }

代碼 13 行,用 PAGED_CODE 宏確保該例程運行在低於 APC_LEVEL 的中斷優先

級的級別上。

代碼 17 行,得到設備擴展結構。

代碼 18 行,得到當前 IRP 的堆棧。設備堆棧是一個很復雜的概念,筆者將在第 4

章進行論述。

代碼 19~52 行,將對應類別的即插即用 IRP 調用做不同的處理,並打印出調試信

息。其中,IRP_MN_REMOVE_DEVICE 由 HandleRemoveDevice 處理,而其他 IRP

則由 DefaultPnpHandler 處理。

1.4.5 HelloWDM 對 PNP 的默認處理

除了 IRP_MN_STOP_DEVICE 以外,HelloWDM 對其他 PNP 的 IRP 做同樣的處理,

即直接傳遞到底層驅動,並將底層驅動的結果返回。

#001 /************************************************************************

#002 * 函數名稱:DefaultPnpHandler

#003 * 功能描述:對 PNP IRP進行默認處理

#004 * 參數列表:

#005

#006

pdx:設備對象的擴展

Irp:從 I/O請求包

#007 * 返回值:返回狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp)

#011 {

#012

#013

#014

#015

#016

PAGED_CODE();

KdPrint(("Enter DefaultPnpHandler\n"));

IoSkipCurrentIrpStackLocation(Irp);

KdPrint(("Leave DefaultPnpHandler\n"));

return IoCallDriver(pdx->NextStackDevice, Irp);

23

 

Windows驅動開發技術詳解

#017 }

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄下找到。

代碼 12 行,確保該例程處於 APC_LEVEL 之下。

代碼 14 行,略過當前堆棧。

代碼 16 行,用下層堆棧的驅動設備對象處理此 IRP。

24

 

第 1 章 從兩個最簡單的驅動談起

1.4.6 HelloWDM 對 IRP_MN_REMOVE_DEVICE 的處理

對 IRP_MN_REMOVE_DEVICE 的處理類似於在 NT 式驅動中的卸載例程,而在 WDM

式的驅動中,卸載例程幾乎不用做處理。

#001 /************************************************************************

#002 * 函數名稱:HandleRemoveDevice

#003 * 功能描述:對 IRP_MN_REMOVE_DEVICE IRP進行處理

#004 * 參數列表:

#005

#006

fdo:功能設備對象

Irp:從 I/O請求包

#007 * 返回值:返回狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)

#011 {

#012

#013

#014

#015

#016

#017

#018

PAGED_CODE();

KdPrint(("Enter HandleRemoveDevice\n"));

Irp->IoStatus.Status = STATUS_SUCCESS;

NTSTATUS status = DefaultPnpHandler(pdx, Irp);

IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);

#019

//調用 IoDetachDevice()把 fdo從設備棧中脫開

#020

#021

#022

if (pdx->NextStackDevice)

IoDetachDevice(pdx->NextStackDevice);

#023

//刪除 fdo

#024

#025

#026

IoDeleteDevice(pdx->fdo);

KdPrint(("Leave HandleRemoveDevice\n"));

return status;

#027 }

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄中找到。

代碼 15 行,設置此 IRP 的狀態為順利完成。

代碼 16 行,調用默認的 PNP 的 IRP 的處理函數。

代碼 17 行,刪除此設備對象的符號鏈接。

代碼 19~21 行,從設備堆棧中卸載此設備對象。

代碼 24 行,刪除設備對象。

1.4.7 HelloWDM 對其他 IRP 的回調函數

此處對創建、關閉、讀寫設備的默認處理同 HellloDDK 中,所以不重復說明。

#001 /************************************************************************

#002 * 函數名稱:HelloWDMDispatchRoutine

25

 

Windows驅動開發技術詳解

#003 * 功能描述:對默認 IRP進行處理

#004 * 參數列表:

#005

#006

fdo:功能設備對象

Irp:從 I/O請求包

#007 * 返回值:返回狀態

#008 *************************************************************************/

#009 #pragma PAGEDCODE

#010 NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,

#011

IN PIRP Irp)

#012 {

#013

PAGED_CODE();

#014

#015

#016

#017

#018

#019

KdPrint(("Enter HelloWDMDispatchRoutine\n"));

Irp->IoStatus.Status = STATUS_SUCCESS;

Irp->IoStatus.Information = 0; // no bytes xfered

IoCompleteRequest( Irp, IO_NO_INCREMENT );

KdPrint(("Leave HelloWDMDispatchRoutine\n"));

return STATUS_SUCCESS;

#020 }

1.4.8 HelloWDM 的卸載例程

由於 WDM 式的驅動程序將主要的卸載任務放在了對 IRP_MN_REMOVE_DEVICE

的處理函數中,在標准的卸載例程幾乎沒有什么需要做的。在這里,僅僅是打印幾行調

試信息。

#001 /************************************************************************

#002 * 函數名稱:HelloWDMUnload

#003 * 功能描述:負責驅動程序的卸載操作

#004 * 參數列表:

#005

DriverObject:驅動對象

#006 * 返回值:返回狀態

#007 *************************************************************************/

#008 #pragma PAGEDCODE

#009 void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject)

#010 {

#011

PAGED_CODE();

#012

#013

KdPrint(("Enter HelloWDMUnload\n"));

KdPrint(("Leave HelloWDMUnload\n"));

#014 }

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄下找到。

1.5 HelloWDM的編譯和安裝

HelloWDM 的編譯和安裝與 HelloDDK 的過程有一些不同,尤其是安裝過程。這主要

是因為 HelloWDM 是即插即用式的驅動程序,並且需要借助 INF 文件安裝。

26

 

第 1 章 從兩個最簡單的驅動談起

1.5.1 用 DDK 編譯環境編譯 HelloWDM

同 HelloDDK 一樣,首先介紹用 DDK 編譯環境編譯,再介紹用 VC6 IDE 的編譯方法。

編譯 HelloWDM 需要編寫兩個腳本文件,makefile 和 Sources。makefile 同 HelloDDK 中的

一樣,這里不再給出。Sources 稍有不同,如下:

TARGETNAME=HelloWDM

TARGETTYPE=DRIVER

DRIVERTYPE=WDM

TARGETPATH=OBJ

INCLUDES=$(BASEDIR)\inc;\

$(BASEDIR)\inc\ddk;\

SOURCES=HelloWDM.cpp\

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄下找到。

將兩個文件和源文件放在同一目錄下,進入“Windows XP Checked Build Environments”

編譯環境。用 cd 進入對應目錄,運行 build,會編譯出相應的 HelloWDM.sys 文件。

1.5.2 HelloWDM 的編譯過程

HelloWDM 的編譯過程和 HelloDDK 的方法類似

用 VC 建立一個新工程,步驟同 HelloDDK。

將兩個源文件 HelloWDM.h 和 HelloWDM.cpp 復制到工程目錄中,並添加到工程

中,步驟同 HelloDDK。

增加新的編譯版本,去掉 Debug 和 Release 版本,步驟同 HelloDDK。

修改工程屬性。在 VC 中選擇“Project”|“Setting”。

選擇 General 選項卡。將“Intermediate files”和“Output files”改 為 MyDriver_Check。

選擇 C/C++選項卡,將原有的 Project Options 內容全部刪除,替換成如下內容:

/nologo /Gz /MLd /W3 /WX /Z7 /Od /D WIN32=100 /D _X86_=1 /D WINVER=0x500 /D DBG=1

/Fo"MyDriver_Check/" /Fd"MyDriver_Check/" /FD /c

選擇 Link 選項卡,將將原有的 Project Options 內容全部刪除,替換成如下內容:

wdm.lib /nologo /base:"0x10000" /stack:0x400000,0x1000 /entry:"DriverEntry"

/subsystem:console /incremental:no /pdb:"MyDriver_Check/HelloWDM.pdb" /debug

/machine:I386 /nodefaultlib /out:"MyDriver_Check/HelloWDM.sys" /pdbtype:sept

/subsystem:native /driver /SECTION:INIT,D /RELEASE /IGNORE:4078

修改 VC 的 lib 目錄和 include 目錄,同 HelloDDK。

編譯。按下 F7 鍵,編譯生成 HelloWDM.sys。

1.5.3 安裝 HelloWDM

WDM 式驅動程序和 NT 式驅動程序的安裝有很大的出入,首先為了安裝 HelloWDM

27

 

Windows驅動開發技術詳解

驅動程序,需要為驅動程序編寫一個 inf 文件。inf 文件描述了 WDM 驅動程序的操作硬件

設備的信息和驅動程序的一些信息。下面是這個 inf 文件的代碼,請將這個文件和其他源

文件放在同一個目錄里。

HelloWDM.inf:

#001 ;; Win2K DDK 文檔中有詳細參考

#002

#003 ;--------- 版本區域 ---------------------------------------------------

#004

#005 [Version]

#006 Signature="$CHICAGO$"

#007 Provider=Zhangfan_Device

#008 DriverVer=11/1/2007,3.0.0.3

#009

#010 ; 如果設備是一個標准類別,使用標准類的名稱和 GUID

#011 ; 否則創建一個自定義的類別名稱,並自定義它的 GUID

#012

#013 Class=ZhangfanDevice

#014 ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0}

#015

#016

#017 ;--------- 安裝磁盤節-----------------------

#018

#019 ; 這些節確定安裝盤和安裝文件的路徑

#020 ;讀者可以按照自己的需要修改

#021

#022 [SourceDisksNames]

#023 1 = "HelloWDM",Disk1,,

#024

#025 [SourceDisksFiles]

#026 HelloWDM.sys = 1,MyDriver_Check,

#027

#028 ;--------- ClassInstall/ClassInstall32 Section -------------------------

#029

#030 ; 如果使用標准類別設備,下面的是不需要的

#031

#032 ; 9X Style

#033 [ClassInstall]

#034 Addreg=Class_AddReg

#035

#036 ; NT Style

#037 [ClassInstall32]

#038 Addreg=Class_AddReg

#039

#040 [Class_AddReg]

#041 HKR,,,,%DeviceClassName%

#042 HKR,,Icon,,"-5"

#043

#044 ;--------- 目標文件節 -------------------------------------------

#045

#046 [DestinationDirs]

28

 

第 1 章 從兩個最簡單的驅動談起

#047 YouMark_Files_Driver = 10,System32\Drivers

#048

#049 ;--------- 制造商節 ----------------------------------

#050

#051 [Manufacturer]

#052 %MfgName%=Mfg0

#053

#054 [Mfg0]

#055

#056 ; 在這里描述 PCI的 VendorID和 ProductID

#057 ; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd

#058 ;改成自己的 ID

#059 %DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999

#060

#061 ;---------- DDInstall Sections -----------------------------------------

#062 ; --------- Windows 9X -----------------

#063

#064 ; 如果在 DDInstall中的字符串超過 19,將會導致嚴重的問題

#065 ;

#066

#067 [YouMark_DDI]

#068 CopyFiles=YouMark_Files_Driver

#069 AddReg=YouMark_9X_AddReg

#070

#071 [YouMark_9X_AddReg]

#072 HKR,,DevLoader,,*ntkern

#073 HKR,,NTMPDriver,,HelloWDM.sys

#074 HKR, "Parameters", "BreakOnEntry", 0x00010001, 0

#075

#076 ; --------- Windows NT -----------------

#077

#078 [YouMark_DDI.NT]

#079 CopyFiles=YouMark_Files_Driver

#080 AddReg=YouMark_NT_AddReg

#081

#082 [YouMark_DDI.NT.Services]

#083 Addservice = HelloWDM, 0x00000002, YouMark_AddService

#084

#085 [YouMark_AddService]

#086 DisplayName = %SvcDesc%

#087 ServiceType = 1 ; SERVICE_KERNEL_DRIVER

#088 StartType = 3 ; SERVICE_DEMAND_START

#089 ErrorControl = 1 ; SERVICE_ERROR_NORMAL

#090 ServiceBinary = %10%\System32\Drivers\HelloWDM.sys

#091

#092 [YouMark_NT_AddReg]

#093 HKLM, "System\CurrentControlSet\Services\HelloWDM\Parameters",\

#094 "BreakOnEntry", 0x00010001, 0

#095

#096

#097 ; --------- 文件節(common) -------------

#098

#099 [YouMark_Files_Driver]

#100 HelloWDM.sys

29

 

Windows驅動開發技術詳解

#101

#102 ;--------- 字符串節--------------------------------------------------

#103

#104 [Strings]

#105 ProviderName="Zhangfan."

#106 MfgName="Zhangfan Soft"

#107 DeviceDesc="Hello World WDM!"

#108 DeviceClassName="Zhangfan_Device"

#109 SvcDesc="Zhangfan"

此段代碼可以在配套光盤中本章的 WDM_Driver 目錄下找到。

HelloWDM 是個虛擬設備,安裝需要如下方法。

首先,在第一次安裝 HelloWDM 驅動程序時,進入控制面板,選擇添加硬件。系

統會自動檢索是否有新設備插入,並彈出對話框詢問是否將硬件連接到計算機,選擇是,

如圖 1-18 所示。

在彈出的下一個對話框中選擇“添加新的硬件設備”,並單擊“下一步”按鈕,如

圖 1-19 所示。

圖 1-18 添加虛擬設備

圖 1-19 選擇添加新硬件

在彈出的下一個對話框中選擇“安裝我手動從列表選擇的硬件(高級)”,並單擊

“下一步”按鈕,如圖 1-20 所示。

在彈出的下一個對話框中選擇顯示所有設備,並單擊“下一步”按鈕,如圖 1-21

所示。

在彈出的下一對話框中選擇“從磁盤安裝”並選擇 inf 文件,如圖 1-22 所示。

最后系統提示安裝成功,如圖 1-23 所示。

30

 

第 1 章 從兩個最簡單的驅動談起

圖 1-20 手動安裝設備

圖 1-21 選擇顯示所有設備

圖 1-22 從磁盤安裝設備驅動

圖 1-23 安裝完畢

和 HelloDDK 一樣,也可以在設備管理器中找到這個虛擬的設備,如圖 1-24 所示。

這種安裝 WDM 驅動程序的方法,過於煩瑣,同時需要等待系統枚舉設備,因此會

等待很久的時間。在這里,筆者向讀者介紹一個快速安裝 WDM 程序的方法。使用一個

叫做 EzDriverInstaller 的工具,這個工具也是 DriverStudio 自帶的一個工具軟件。打開

EzDriverInstaller,選擇 “File”|“Open”,在彈出的對話框中,選擇需要安裝的 inf 文件,

單擊“Add New Device”按鈕,如圖 1-25 所示。很快地,驅動程序就被加載了,

EzDriverInstaller 還提供了刪除驅動程序功能、開啟/關閉驅動功能、屏蔽驅動功能、重啟

驅動功能等。在調試驅動的時候,EzDriverInstaller 不失為一個很好的加載 WDM 驅動程

序的工具。

31

 

Windows驅動開發技術詳解

圖 1-24 在設備管理器中顯示 HelloWDM 設備

圖 1-25 用 EzDriverInstaller 安裝 HelloWDM 驅動

1.6 小結

本章筆者帶領讀者編寫了兩個非常簡單的驅動程序——HelloDDK 和 HelloWDM,並

詳細說明了這兩個驅動程序的編譯過程、安裝過程。筆者在初學 Windows 驅動程序開發的

時候,對編譯的方法摸索了很久,尤其是用 VC 編譯驅動程序(其實用 VC 還是 DDK 環

境編譯本質完全一樣,都是用 cl.exe 進行編譯)。這方面的資料非常少,就連微軟官方的

DDK 文檔中都很少有介紹。通過本章的學習,讀者能很快地將這兩個驅動程序編譯並且

順利安裝。在以后的章節里,筆者會陸續對這兩個驅動程序做深入的剖析,並且重復利用

這兩個驅動程序的框架。驅動程序的代碼量往往都很小,在這兩個驅動程序的基礎上,就

能編寫出很多有意思的驅動程序來。相信讀者已經進入 Windows 驅動開發的大門了。

32


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM