***************************************************************/
//一定要的標頭檔,聲明了函數模組和變數:
#include "HelloWDM.h"
/***************************************************************
函數名稱:DriverEntry()
功能描述:WDM程式入口
***************************************************************/
//extern "C"是必須的,表示“用C連結”。如果你的檔案名稱是HelloWDM.c的話,這句可以省略。
extern "C"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
//指定“添加裝置”訊息由函數“HelloWDMAddDevice()”來處理:
DriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
//指定“隨插即用”訊息由函數“HelloWDMPnp()”來處理:
DriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
//返回一個NTSTATUS值STATUS_SUCCESS。幾乎所有的驅動程式常式都必須返回一個NTSTATUS值,這些值在NTSTATUS.H DDK標頭檔中有詳細的定義。
return STATUS_SUCCESS;
}
/***************************************************************
函數名稱:HelloWDMAddDevice()
功能描述:處理“添加裝置”訊息
***************************************************************/
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
//定義一個NTSTATUS類型的傳回值:
NTSTATUS status;
//定義一個功能裝置對象(Functional Device Object):
PDEVICE_OBJECT fdo;
//建立我們的功能裝置對象,並儲存到fdo中:
status = IoCreateDevice(
DriverObject, //驅動程式對象
sizeof(DEVICE_EXTENSION), //要求的裝置擴充的大小
NULL, //裝置名稱,這裡為NULL
FILE_DEVICE_UNKNOWN, //裝置的類型,在標準標頭檔WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
0, //各種常量用OR組合在一起,指示可刪除介質、唯讀等。
FALSE, //如果一次只有一個線程可以訪問該裝置,為TRUE,否則為FALSE
&fdo); //返回的裝置對象
//NT_SUCCESS宏用於測試IoCreateDevice核心是否成功完成。不要忘記檢查對核心的所有調用是否成功。NT_ERROR宏不等同於!NT_SUCCESS,最好使用!NT_SUCCESS,因為除了錯誤外,它還截獲警告資訊。
if( !NT_SUCCESS(status))
return status;
//建立一個裝置擴充項物件dx,用於儲存指向fdo的指標:
PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
dx->fdo = fdo;
//用IoAttachDeviceToDeviceStack函數把HelloWDM裝置掛接到裝置棧:
dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
//設定fdo的flags。有兩個“位”是必須改變的,一個是必須清除DO_DEVICE_INITIALIZING標誌,如果在DriverEntry常式中調用IoCreateDevice(),就不需要清除這個標誌位。還有一個是必須設定DO_BUFFER_IO標誌位:
fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
fdo->Flags &= ~DO_DEVICE_INITIALIZING;
//傳回值:
return STATUS_SUCCESS;
}
/***************************************************************
函數名稱:HelloWDMPnp()
功能描述:處理“隨插即用”訊息
***************************************************************/
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp)
{
//建立一個裝置擴充項物件dx,用於儲存指向fdo的指標:
PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
//首先要通過函數IoGetCurrentIrpStackLocation()得到當前的IRP,並由此得到Minor Function:
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG MinorFunction = IrpStack->MinorFunction;
//然後把這個Minor Function傳遞給下一個裝置棧:
IoSkipCurrentIrpStackLocation(Irp);
NTSTATUS status = IoCallDriver( dx->NextStackDevice, Irp);
//處理“隨插即用”次功能代碼:
//當Minor Function等於IRP_MN_REMOVE_DEVICE時,說明有裝置被拔出或卸下,這時要取消資源分派並刪除裝置:
if( MinorFunction==IRP_MN_REMOVE_DEVICE)
{
//取消裝置介面:
IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);
RtlFreeUnicodeString(&dx->ifSymLinkName);
//調用IoDetachDevice()把fdo從裝置棧中脫開:
if (dx->NextStackDevice)
IoDetachDevice(dx->NextStackDevice);
//刪除fdo:
IoDeleteDevice(fdo);
}
//傳回值:
return status;
}
/***************************************************************
程式名稱:Hello World for WDM
檔案名稱:HelloWDM.h
作者:羅聰
日期:2002-8-16
***************************************************************/
//標頭檔,只是聲明一些函數和變數,比較簡單就不多說了,請讀者自行研究:
#ifdef __cplusplus
extern "C"
{
#endif
#include "ntddk.h"
#ifdef __cplusplus
}
#endif
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
PDEVICE_OBJECT NextStackDevice;
UNICODE_STRING ifSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject);
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp);
好了,第一個WDM版的“Hello World”就介紹到這裡,雖然實際上它什麼都沒有做,但是由於它包含了完整的架構,所以對於初學者來說還是很有參考價值的。至於怎麼編譯及安裝,留待下次再講,敬請留意。(不是我想賣關子啊,這些步驟實在是很麻煩的,要另外寫一篇文章才說得清楚哦!)
WDM驅動程式入門(2)——驅動程式的小秘密
好啦,辛辛苦苦終於寫完了程式,讓我們編譯運行吧!按下Ctrl+F5(嘿嘿,讓我們先假設你習慣用VC來寫程式),我等啊等……疑?怎麼毫無動靜的?再看看Output視窗,哇!有幾百個錯誤啊!!不禁頭大——這是怎麼回事呢?
原來,WDM程式編譯出來的並不是我們常見的.exe,而是.sys檔案,在未經設定編譯環境之前,是不能直接用VC來編譯的(這就是為什麼會有幾百個錯誤了)。這種類型的檔案你可以在WINNT/System32/Drivers裡面找到很多。其實驅動程式也是一種PE檔案,它同樣由DOS MZ header開頭,也有完整的DOS stub和PE header,同樣擁有Import table和Export table——hoho……那跟普通的PE檔案有什麼不一樣呢?偉大的領袖毛主席教育我們,實踐是檢驗真理的唯一標準。那麼就讓我們先來做個小剖析,加深對.sys檔案的認識吧!(如果你對.sys的內部細節沒有興趣的話,可以略過不看。^_^)
首先祭出Delphi裡附帶的tdump.exe程式(別問我為什麼用這個,這隻是純粹的習慣問題)。讓我們鍵入:
C:/WINNT/System32/Drivers>tdump ccport.sys -em -ee
參數-em是列出Import table,-ee是列出Export table。斷行符號之後,螢幕列出一大堆東西:
C:/WINNT/SYSTEM32/DRIVERS>tdump ccport.sys -em -ee
Turbo Dump Version 5.0.16.12 Copyright ? 1988, 2000 Inprise Corporation
Display of File CCPORT.SYS
IMPORT: NTOSKRNL.EXE={hint:011Fh}.’memcpy’
IMPORT: NTOSKRNL.EXE={hint:003Dh}.’IoDeleteDevice’
IMPORT: NTOSKRNL.EXE={hint:0030h}.’IoAttachDeviceToDeviceStack’
IMPORT: NTOSKRNL.EXE={hint:008Eh}.’KeSetEvent’
IMPORT: NTOSKRNL.EXE={hint:0068h}.’IofCallDriver’
IMPORT: NTOSKRNL.EXE={hint:0095h}.’KeWaitForSingleObject’
IMPORT: NTOSKRNL.EXE={hint:0074h}.’KeInitializeEvent’
IMPORT: NTOSKRNL.EXE={hint:003Fh}.’IoDetachDevice’
IMPORT: NTOSKRNL.EXE={hint:00D3h}.’RtlFreeUnicodeString’
IMPORT: NTOSKRNL.EXE={hint:0077h}.’KeInitializeSpinLock’
IMPORT: NTOSKRNL.EXE={hint:0129h}.’strcpy’
IMPORT: NTOSKRNL.EXE={hint:0121h}.’memset’
IMPORT: NTOSKRNL.EXE={hint:003Ch}.’IoCreateUnprotectedSymbolicLink’
IMPORT: NTOSKRNL.EXE={hint:0038h}.’IoCreateDevice’
IMPORT: NTOSKRNL.EXE={hint:00C2h}.’RtlAnsiStringToUnicodeString’
IMPORT: NTOSKRNL.EXE={hint:0069h}.’IofCompleteRequest’
IMPORT: NTOSKRNL.EXE={hint:0124h}.’sprintf’
IMPORT: NTOSKRNL.EXE={hint:003Eh}.’IoDeleteSymbolicLink’
IMPORT: NTOSKRNL.EXE={hint:0042h}.’IoFreeIrp’
IMPORT: NTOSKRNL.EXE={hint:004Dh}.’IoInitializeIrp’
IMPORT: NTOSKRNL.EXE={hint:002Dh}.’IoAllocateIrp’
IMPORT: NTOSKRNL.EXE={hint:0027h}.’InterlockedExchange’
IMPORT: NTOSKRNL.EXE={hint:0025h}.’InterlockedCompareExchange’
IMPORT: NTOSKRNL.EXE={hint:0035h}.’IoCancelIrp’
IMPORT: NTOSKRNL.EXE={hint:012Ah}.’strlen’
IMPORT: NTOSKRNL.EXE={hint:0126h}.’strcat’
IMPORT: NTOSKRNL.EXE={hint:0114h}.’atoi’
IMPORT: NTOSKRNL.EXE={hint:0128h}.’strcmp’
IMPORT: NTOSKRNL.EXE={hint:0034h}.’IoBuildSynchronousFsdRequest’
IMPORT: NTOSKRNL.EXE={hint:00D5h}.’RtlInitAnsiString’
IMPORT: HAL.DLL={hint:0006h}.’KfAcquireSpinLock’
IMPORT: HAL.DLL={hint:0009h}.’KfReleaseSpinLock’
EXPORT ord:0001=’Vcomm_DriverControl’
我們可以很清楚地看到,它主要調用了NTOSKRNL.EXE和HAL.DLL檔案(實際上你會發現,幾乎所有的WDM驅動程式都會調用NTOSKRNL.EXE檔案,從它的名字你可以看出為什麼了吧?),並且輸出了一個函數“Vcomm_DriverControl”。這表明,其實.sys跟.exe檔案一樣,都是一種PE檔案來的。不同的是,.sys檔案Import的通常是NTOSKRNL.EXE,而.exe檔案Import的通常是KERNEL32.DLL和USER32.DLL。
知道了這些有什麼用呢?實際上,由於.sys通常不調用KERNEL32.DLL和USER32.DLL,所以你是不能在裝置驅動程式裡面調用任何C、C++和Win32函數的,而且也不能用C++關鍵字new和delete等(可以用malloc和free來代替),而必須使用大量的核心功能。為了讀者的方便,下面我列出一些常見的驅動程式可用的核心功能:
Ex… 執行支援
Hal… 硬體抽象層(僅NT/Windows 2000)
Io… I/O管理器(包括隨插即用函數)
Ke… 核心
Ks… 核心流IRP管理函數
Mm… 記憶體管理器
Ob… 對象管理器
Po… 電源管理
Ps… 進程結構
Rtl… 執行階段程式庫
Se… 安全引用監視
Zw… 其他函數
最後讓我們再來看看,寫裝置驅動程式時必須注意的一些問題:
1、核心宏
如果查看DDK標頭檔,會發現有幾個核心功能是以宏的方式實現的。這種宏中有幾個宏的定義是相當糟糕的。例如,我們看到RemoveHeadList的定義如下:
#define RemoveHeadList(ListHead)
(ListHead)->Flink;
{RemoveEntryList((ListHead)->Flink)}
如果以以下方式調用RemoveHeadList,則將編譯錯誤的代碼:
if(SomethingInList)
Entry = RemoveHeadList(list);
使這個調用安全的唯一方法是使用花括弧:
if(SomethingInList)
{
Entry = RemoveHeadList(list);
}
所以我們切勿為了貪圖一時的方便,而使用不太規範的寫法,最好是在所有的if、for和while等語句中使用花括弧。
2、驅動程式函數名稱
跟C/C++的main()函數一樣,裝置驅動程式也有一個必須存在,而且只能以DriverEntry()為名稱的入口函數。然而,除此之外,我們可以使用任何名字來給其他函數命名——只要你自己記得就行了,當然,最好符合某些特定的規範啦,例如匈牙利命名法……
3、安裝時的問題
·在Windows98中驅動程式可執行檔必須是8.3檔案名稱。(別問我為什麼,我也不知道,我只能建議你去問比爾該死)
·如果INF檔案中含有非法節的詳細資料,Windows將不使用這個INF檔案。
呼~~講了那麼多,先去喝口水吧。其實本節羅羅嗦嗦講了一大堆,跟實際的編程卻並沒有太大的關係,不過為了將來養成良好的編程習慣,還是應該遵守一下的,對嗎?好了,下一節我將詳細講解如何編譯、安裝驅動程式,敬請留意。
WDM驅動程式入門(3)——安裝步驟
8 DDK和2000 DDK兩種,它們工作起來是大同小異的,不過有些驅動程式只能在2000 DDK中使用。由於Win98註定是一種即將被淘汰的作業系統了,所以我也不打算介紹如何在98 DDK中進行編譯,以下的所有內容都是針對2000 DDK的。
·準備工作
1、確定你已經安裝了Visual C++
2、安裝2000 DDK
3、安裝2000 DDK成功後,在“開始 ”->“程式 ”裡應該有“Development Kits ”->“Windows 2000 DDK ”的項目。
(注意一定要先安裝好VC,然後才安裝DDK,這個順序決不能顛倒!!)
4、保證DDKROOT環境變數設定為Windows 2000 DDK的基目錄,如果不是的話,請在控制台“系統 ”屬性的“進階 ”標籤環境變數編輯器中設定好這個環境變數。
·編寫必需的檔案
編譯WDM程式的時候,有兩個檔案是必須要有的,它們是:
1、makefile
(這個是什麼啊?你可能會問。)對於比較年輕的程式員來說,有可能沒有見過這個檔案吧。其實在VC這些IDE出現之前,我們都必須使用makefile來確定項目中哪些檔案需要重新編譯,現在的IDE都把這個工作自動做好了。(Well……其實這樣也好。)
我們要做的工作很簡單,就是提供這樣一個檔案,它的內容是:
## DO NOT EDIT THIS FILE!!! Edit ./sources. If you want to add a new source# file to this component. This file merely indirects to the real make file# that is shared by all the driver components of the Windows NT DDK#!INCLUDE $(NTMAKEENV)/makefile.def
正如它所述,不要編輯這個檔案。事實上每個WDM程式所需要的makefile的內容都是一樣的,也就是說,我們只需要簡單地copy一個makefile到新的項目中就可以了。(呵呵,是不是很方便呢?)
2、Sources
TARGETNAME=HelloWDMTARGETTYPE=DRIVERDRIVERTYPE=WDMTARGETPATH=OBJINCLUDES=$(BASEDIR)/inc;/ $(BASEDIR)/inc/ddk;/TARGETLIBS=$(BASEDIR)/lib/*/free/usbd.lib/SOURCES=HelloWDM.cpp/
這個檔案指定了驅動程式目標名是HelloWDM.sys,是一個WDM驅動程式,產生的檔案存放在OBJ目錄中。值得注意的是,“=”前後不能有空格,否則編譯的時候會出錯。
·開始編譯
娃哈哈,前面羅羅嗦嗦講了一大堆,現在終於到重點了。WDM程式的編譯過程比較特殊,它不是在VC裡面按F7來編譯的(儘管你可以通過設定來達到這一目的),而是通過一個DDK工具 + 生產力build來完成。下面我們來講講具體步驟:
1、“Debug”版的產生
首先,我們假設你的原始碼放在D:/HelloWDM裡面。請跟著以下步驟:
“開始”->“程式”->“Development Kits”->“Windows 2000 DDK”->“Checked Build Environment”
螢幕將顯示:(有“斷行符號”的那行是需要讀者你親自打進去的)
New or updated MSVC detected. Updating DDK environment….Setting environment for using Microsoft Visual C++ tools.Starting dirs creation…Completed.D:/NTDDK>cd/HelloWDM (斷行符號)D:/HelloWDM>build (斷行符號)
如果原始碼沒有錯誤的話,產生的HelloWDM.sys將存放在objchk/i386目錄中。
2、“Release”版的產生
請跟著以下步驟:
“開始”->“程式”->“Development Kits”->“Windows 2000 DDK”->“Free Build Environment”
隨後的步驟跟“Debug”版相同,不同的是產生的HelloWDM.sys將存放在objfre/i386目錄中。
·安裝
如果前面的編譯過程沒有錯誤的話,現在我們應該已經得到了一個HelloWDM.sys檔案,假設它是放在D:/HelloWDM/objfre/i386中。
我們還要幹什麼呢?…………對啦,就是安裝它!不然辛辛苦苦編譯出來有什麼用?
安裝WDM驅動程式可以用兩種方法,一種是利用註冊表,還有一種是利用INF檔案。我們一般是採用INF檔案(這是微軟推薦的)。INF檔案可以在 WINNT/INF 目錄中找到很多。為了順利安裝,我在這裡先給出 HelloWDM 所需要的 HelloWDM.INF 檔案:
;; The Win2K DDK documentation contains an excellent INF reference.;--------- Version Section ---------------------------------------------------[Version]Signature="$CHICAGO$"Provider=LC_DeviceDriverVer=8/21/2002,3.0.0.3; If device fits one of the standard classes, use the name and GUID here,; otherwise create your own device class and GUID as this example shows.Class=UnknownClassGUID={ff646f80-8def-11d2-9449-00105a075f6b};--------- SourceDiskNames and SourceDiskFiles Section -----------------------; These sections identify source disks and files for installation. They are; shown here as an example, but commented out.[SourceDisksNames]1 = "HelloWDM",Disk1,,[SourceDisksFiles]HelloWDM.sys = 1,objfre/i386,;--------- ClassInstall/ClassInstall32 Section -------------------------------; Not necessary if using a standard class; 9X Style[ClassInstall]Addreg=Class_AddReg; NT Style[ClassInstall32]Addreg=Class_AddReg[Class_AddReg]HKR,,,,%DeviceClassName%HKR,,Icon,,"-5";--------- DestinationDirs Section -------------------------------------------[DestinationDirs]YouMark_Files_Driver = 10,System32/Drivers;--------- Manufacturer and Models Sections ----------------------------------[Manufacturer]%MfgName%=Mfg0[Mfg0]; PCI hardware Ids use the form; PCI/VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd;改成你自己的ID%DeviceDesc%=YouMark_DDI, PCI/VEN_9999&DEV_9999;---------- DDInstall Sections -----------------------------------------------; --------- Windows 9X -----------------; Experimentation has shown that DDInstall root names greater than 19 characters; cause problems in Windows 98[YouMark_DDI]CopyFiles=YouMark_Files_DriverAddReg=YouMark_9X_AddReg[YouMark_9X_AddReg]HKR,,DevLoader,,*ntkernHKR,,NTMPDriver,,HelloWDM.sysHKR, "Parameters", "BreakOnEntry", 0x00010001, 0; --------- Windows NT -----------------[YouMark_DDI.NT]CopyFiles=YouMark_Files_DriverAddReg=YouMark_NT_AddReg[YouMark_DDI.NT.Services]Addservice = HelloWDM, 0x00000002, YouMark_AddService[YouMark_AddService]DisplayName = %SvcDesc%ServiceType = 1 ; SERVICE_KERNEL_DRIVERStartType = 3 ; SERVICE_DEMAND_STARTErrorControl = 1 ; SERVICE_ERROR_NORMALServiceBinary = %10%/System32/Drivers/HelloWDM.sys[YouMark_NT_AddReg]HKLM, "System/CurrentControlSet/Services/HelloWDM/Parameters",/"BreakOnEntry", 0x00010001, 0; --------- Files (common) -------------[YouMark_Files_Driver]HelloWDM.sys;--------- Strings Section ---------------------------------------------------[Strings]ProviderName="Flying L Co.,Ltd."MfgName="LC Soft"DeviceDesc="Hello World WDM!"DeviceClassName="LC_Device"SvcDesc="???"
注意它可以同時在Win98或者Win2000中使用(系統會通過這個INF檔案裡面的欄位名稱,自動選擇適合當前系統的安裝方法的)。由於INF檔案的各個欄位含義比較複雜,限於篇幅,我在這裡就不詳細講解了,請讀者自行參閱有關的文章或者書籍。
準備好這個 HelloWDM.INF 檔案後,讓我們開啟控制台,雙擊“添加/刪除硬體”,選擇“添加/排除裝置故障”->“添加新裝置”->“否,我想從列表選擇硬體”->“其它裝置”->“從磁碟安裝”,選擇 HelloWDM.INF 所在的路徑,然後安裝。
當安裝完成後,系統就會添加上你寫好的驅動程式了。(可以在“裝置管理員”中查看到)。然後重啟電腦,這個驅動程式就投入使用啦。