天天看點

Windows驅動開發WDM (2)- 一個簡單的WDM驅動程式

這個例子是從《windows驅動開發技術詳解》的CD光牒上copy的,我隻是自己稍微改了一下。

入口函數DriverEntry

#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
								IN PUNICODE_STRING pRegistryPath)
{
	KdPrint(("Enter DriverEntry\n"));

	pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
	pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
	pDriverObject->MajorFunction[IRP_MJ_READ] = 
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
	pDriverObject->DriverUnload = HelloWDMUnload;

	KdPrint(("Leave DriverEntry\n"));
	return STATUS_SUCCESS;
}
           

提供4個派遣函數:

HelloWDMAddDevice

HelloWDMPnp

HelloWDMDispatchRoutine

HelloWDMUnload

在DriverEntry裡面簡單的設定一下。

AddDevice函數

AddDevice函數是WDM驅動特有的,NT驅動沒有,這也是主要的差別之一。先給出代碼:

#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
//DriverObject就是指向本驅動程式的一個對象,是由PNP管理器傳遞進來的。
//PhysicalDeviceObject是PNP管理器傳遞進來的底層驅動裝置對象,這個東西在NT驅動中是沒有的。通常稱之為PDO,确切的說是由總線驅動建立的。
{ 
	PAGED_CODE();
	KdPrint(("Enter HelloWDMAddDevice\n"));

	NTSTATUS status;
	PDEVICE_OBJECT fdo;
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//裝置名稱,裝置名稱隻能被核心模式下的其他驅動所識别。

	//建立FDO(Function Device Object)
	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;
	//将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下層裝置。如果PDO上面有過濾驅動的話,NextStackDevice就是過濾驅動,如果沒有就是PDO。
	pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
	UNICODE_STRING symLinkName;
	//建立連結符号,這樣使用者模式的應用就可以通路此裝置。核心模式下,符号連結是以\??\開頭的(或者\DosDevices\)。使用者模式下則是\\.\開頭。
	//這裡就可以在使用者模式下用\\.\HelloWDM來通路本裝置。
	RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");

	pdx->ustrDeviceName = devName;
	pdx->ustrSymLinkName = symLinkName;
	status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);

	if( !NT_SUCCESS(status))
	{
		IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
		status = IoCreateSymbolicLink(&symLinkName,&devName);
		if( !NT_SUCCESS(status))
		{
			return status;
		}
	}

	fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定義為“緩沖記憶體裝置”
	fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保證裝置初始化完畢,必須的。

	KdPrint(("Leave HelloWDMAddDevice\n"));
	return STATUS_SUCCESS;
}
           

IoCreateDevice有個裝置類型參數,這裡使用FILE_DEVICE_UNKNOWN。Windows已經預先定義了一些常見的裝置類型,如果驅動裝置并不在這些類型裡面,有兩種辦法:

1. 使用FILE_DEVICE_UNKNOWN;

2. 使用>=0x8000(32768 - 65535)的值

詳見:http://msdn.microsoft.com/en-us/library/windows/hardware/ff563821(v=vs.85).aspx

在DriverEntry函數裡面通過pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;設定AddDevice的例程函數。我畫了一個簡單的圖來描述AddDevice基本流程:

Windows驅動開發WDM (2)- 一個簡單的WDM驅動程式

 AddDevice函數有2個參數:DriverObject和PDO。

DriverObject就是目前驅動程式的一個執行個體,PDO是實體裝置對象(由總線驅動建立)。

AddDevice函數主要工作就是建立一個功能裝置對象FDO,然後附加在傳遞進來的PDO上面。

PNP IRP處理函數

WDM支援PNP(即插即用),這也是不同于NT驅動的一個主要特征。所有WDM驅動都需要設定PNP IRP的派遣函數。如:

pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
           

這個是在入口函數DriverEntry裡面設定的。

看具體代碼:

#pragma PAGEDCODE
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp)
{
	PAGED_CODE();

	KdPrint(("Enter HelloWDMPnp\n"));
	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice裡面建立的。
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//擷取irp資訊
	switch (stack->MinorFunction)//根據PNP irp的minor function code來調用相應的處理函數。
	{
	case IRP_MN_REMOVE_DEVICE:
		status = HandleRemoveDevice(pdx, Irp);
		break;
	default:
		status = DefaultPnpHandler(pdx, Irp);
		break;
	}
	
	KdPrint(("Leave HelloWDMPnp\n"));
	return status;
}
           

Pnp派遣函數有2個參數:fdo和Irp。

fdo就是由AddDevice函數建立的那個功能裝置對象,Irp是I/O管理器傳進來的一個包(I/O Request Packet)。

這個例子裡面簡單處理了IRP_MN_REMOVE_DEVICE這個minor function code,其他所有的minor function code統統用一個函數來處理DefaultPnpHandler。

先看一下IRP_MN_REMOVE_DEVICE的處理函數:

#pragma PAGEDCODE
NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter HandleRemoveDevice\n"));

	//設定IRP的完成狀态。
	Irp->IoStatus.Status = STATUS_SUCCESS;
	//将IRP請求向底層驅動轉發。
	NTSTATUS status = DefaultPnpHandler(pdx, Irp);

	//删除符号連結
	IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);

    //調用IoDetachDevice()把fdo從裝置棧中脫開:
    if (pdx->NextStackDevice)
        IoDetachDevice(pdx->NextStackDevice);
	
    //删除fdo:
    IoDeleteDevice(pdx->fdo);
	KdPrint(("Leave HandleRemoveDevice\n"));
	return status;
}
           

裡面做了一些簡單的删除工作: 設定IRP完成狀态 -> 将IRP請求向下一層驅動轉發 -> 删除符号連結 -> 删除功能裝置對象(FDO)。

再看一些DefaultPnpHandler函數:

#pragma PAGEDCODE
NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter DefaultPnpHandler\n"));
	IoSkipCurrentIrpStackLocation(Irp);
	KdPrint(("Leave DefaultPnpHandler\n"));
	return IoCallDriver(pdx->NextStackDevice, Irp);//将irp傳遞給下層驅動
}
           

啥也沒做,就是跳過本層堆棧,直接将irp傳遞到下層驅動。

IoSkipCurrentIrpStackLocation:修改IRP的IO_STACK_LOCATION,使下層驅動可以獲得跟本層驅動一樣的IRP。

MSDN解釋:

The IoSkipCurrentIrpStackLocation macro modifies the system'sIO_STACK_LOCATION array pointer, so that when the current driver calls the next-lower driver, that driver receives the sameIO_STACK_LOCATION structure that the current driver received.

 IoCallDriver: 将irp傳遞給下層驅動裝置對象。

現在可以看到,這個驅動程式例子隻是處理了Remove Device,對于其他所有PNP請求,隻是簡單的傳遞給下層驅動處理。

DriverUnload函數

因為IRP_MN_REMOVE_DEVICE的函數裡面已經處理了裝置删除,DriverUnload函數裡面不需要處理什麼事情了,隻是簡單列印一些log。

DriverEntry函數裡面的代碼,簡單設定一下派遣函數,如:

pDriverObject->DriverUnload = HelloWDMUnload;
           

HelloWDMUnload的實作:

#pragma PAGEDCODE
void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject)
{
	PAGED_CODE();
	KdPrint(("Enter HelloWDMUnload\n"));
	KdPrint(("Leave HelloWDMUnload\n"));
}
           

啥都沒做,就是列印log。

最後剩下一個函數是一個派遣函數,處理CREATE,READ, WRITE等irp。

DispatchRoutine

看DriverEntry裡面的幾行代碼:

pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
	pDriverObject->MajorFunction[IRP_MJ_READ] = 
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
           

這個驅動例子裡面,将DEVICE_CONTROL, CREATE, READ 和WRITE的IRP用同一個派遣函數來處理。

#pragma PAGEDCODE
NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,
								 IN PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter HelloWDMDispatchRoutine\n"));
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
	//列印IRP的major function code和minor function code。
	ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
	KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", 
		stack->MajorFunction, stack->MinorFunction, code));

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;	// no bytes xfered
	IoCompleteRequest( Irp, IO_NO_INCREMENT );
	KdPrint(("Leave HelloWDMDispatchRoutine\n"));
	return STATUS_SUCCESS;
}
           

HelloWDMDispatchRoutine是一個派遣函數,它有2個參數:fdo和irp(跟PNP處理函數一樣)

這裡隻做了2個事情:

1. 列印一些log

2. 設定irp完成狀态,調用IoCompleteRequest函數來完成這個irp。不需要往下層驅動傳irp了,因為這個irp已經完成了。

好了,代碼基本介紹完畢,看一下完整代碼:

/************************************************************************
* 檔案名稱:HelloWDM.cpp                                                 
* 作    者:張帆
* 完成日期:2007-11-1
*************************************************************************/
#include "HelloWDM.h"

/************************************************************************
* 函數名稱:DriverEntry
* 功能描述:初始化驅動程式,定位和申請硬體資源,建立核心對象
* 參數清單:
      pDriverObject:從I/O管理器中傳進來的驅動對象
      pRegistryPath:驅動程式在系統資料庫的中的路徑
* 傳回 值:傳回初始化驅動狀态
*************************************************************************/
#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
								IN PUNICODE_STRING pRegistryPath)
{
	KdPrint(("Enter DriverEntry\n"));

	pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
	pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
	pDriverObject->MajorFunction[IRP_MJ_READ] = 
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
	pDriverObject->DriverUnload = HelloWDMUnload;

	KdPrint(("Leave DriverEntry\n"));
	return STATUS_SUCCESS;
}

/************************************************************************
* 函數名稱:HelloWDMAddDevice
* 功能描述:添加新裝置
* 參數清單:
      DriverObject:從I/O管理器中傳進來的驅動對象
      PhysicalDeviceObject:從I/O管理器中傳進來的實體裝置對象
* 傳回 值:傳回添加新裝置狀态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
//DriverObject就是指向本驅動程式的一個對象,是由PNP管理器傳遞進來的。
//PhysicalDeviceObject是PNP管理器傳遞進來的底層驅動裝置對象,這個東西在NT驅動中是沒有的。通常稱之為PDO,确切的說是由總線驅動建立的。
{ 
	PAGED_CODE();
	KdPrint(("Enter HelloWDMAddDevice\n"));

	NTSTATUS status;
	PDEVICE_OBJECT fdo;
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//裝置名稱,裝置名稱隻能被核心模式下的其他驅動所識别。

	//建立FDO(Function Device Object)
	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;
	//将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下層裝置。如果PDO上面有過濾驅動的話,NextStackDevice就是過濾驅動,如果沒有就是PDO。
	pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
	UNICODE_STRING symLinkName;
	//建立連結符号,這樣使用者模式的應用就可以通路此裝置。核心模式下,符号連結是以\??\開頭的(或者\DosDevices\)。使用者模式下則是\\.\開頭。
	//這裡就可以在使用者模式下用\\.\HelloWDM來通路本裝置。
	RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");

	pdx->ustrDeviceName = devName;
	pdx->ustrSymLinkName = symLinkName;
	status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);

	if( !NT_SUCCESS(status))
	{
		IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
		status = IoCreateSymbolicLink(&symLinkName,&devName);
		if( !NT_SUCCESS(status))
		{
			return status;
		}
	}

	fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定義為“緩沖記憶體裝置”
	fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保證裝置初始化完畢,必須的。

	KdPrint(("Leave HelloWDMAddDevice\n"));
	return STATUS_SUCCESS;
}

/************************************************************************
* 函數名稱:DefaultPnpHandler
* 功能描述:對PNP IRP進行預設處理
* 參數清單:
      pdx:裝置對象的擴充
      Irp:從IO請求包
* 傳回 值:傳回狀态
*************************************************************************/ 
#pragma PAGEDCODE
NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter DefaultPnpHandler\n"));
	IoSkipCurrentIrpStackLocation(Irp);
	KdPrint(("Leave DefaultPnpHandler\n"));
	return IoCallDriver(pdx->NextStackDevice, Irp);//将irp傳遞給下層驅動
}

/************************************************************************
* 函數名稱:HandleRemoveDevice
* 功能描述:對IRP_MN_REMOVE_DEVICE IRP進行處理
* 參數清單:
      fdo:功能裝置對象
      Irp:從IO請求包
* 傳回 值:傳回狀态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter HandleRemoveDevice\n"));

	//設定IRP的完成狀态。
	Irp->IoStatus.Status = STATUS_SUCCESS;
	//将IRP請求向底層驅動轉發。
	NTSTATUS status = DefaultPnpHandler(pdx, Irp);

	//删除符号連結
	IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);

    //調用IoDetachDevice()把fdo從裝置棧中脫開:
    if (pdx->NextStackDevice)
        IoDetachDevice(pdx->NextStackDevice);
	
    //删除fdo:
    IoDeleteDevice(pdx->fdo);
	KdPrint(("Leave HandleRemoveDevice\n"));
	return status;
}

/************************************************************************
* 函數名稱:HelloWDMPnp
* 功能描述:對即插即用IRP進行處理
* 參數清單:
      fdo:功能裝置對象
      Irp:從IO請求包
* 傳回 值:傳回狀态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp)
{
	PAGED_CODE();

	KdPrint(("Enter HelloWDMPnp\n"));
	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice裡面建立的。
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//擷取irp資訊
	switch (stack->MinorFunction)//根據PNP irp的minor function code來調用相應的處理函數。
	{
	case IRP_MN_REMOVE_DEVICE:
		status = HandleRemoveDevice(pdx, Irp);
		break;
	default:
		status = DefaultPnpHandler(pdx, Irp);
		break;
	}
	
	KdPrint(("Leave HelloWDMPnp\n"));
	return status;
}

/************************************************************************
* 函數名稱:HelloWDMDispatchRoutine
* 功能描述:對預設IRP進行處理
* 參數清單:
      fdo:功能裝置對象
      Irp:從IO請求包
* 傳回 值:傳回狀态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,
								 IN PIRP Irp)
{
	PAGED_CODE();
	KdPrint(("Enter HelloWDMDispatchRoutine\n"));
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
	//列印IRP的major function code和minor function code。
	ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
	KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", 
		stack->MajorFunction, stack->MinorFunction, code));

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;	// no bytes xfered
	IoCompleteRequest( Irp, IO_NO_INCREMENT );
	KdPrint(("Leave HelloWDMDispatchRoutine\n"));
	return STATUS_SUCCESS;
}

/************************************************************************
* 函數名稱:HelloWDMUnload
* 功能描述:負責驅動程式的解除安裝操作
* 參數清單:
      DriverObject:驅動對象
* 傳回 值:傳回狀态
*************************************************************************/
#pragma PAGEDCODE
void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject)
{
	PAGED_CODE();
	KdPrint(("Enter HelloWDMUnload\n"));
	KdPrint(("Leave HelloWDMUnload\n"));
}
           

 接下來就是如何安裝了。

WDM驅動安裝

WDM驅動安裝需要一個inf檔案,這裡就直接引用《windows驅動開發技術詳解》裡面的,沒有做任何改動,直接貼出來:

;; The Win2K DDK documentation contains an excellent INF reference.

;--------- Version Section ---------------------------------------------------

[Version]
Signature="$CHICAGO$"
Provider=Zhangfan_Device
DriverVer=11/1/2007,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=ZhangfanDevice
ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0}


;--------- 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,MyDriver_Check,

;--------- 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_Driver
AddReg=YouMark_9X_AddReg

[YouMark_9X_AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,HelloWDM.sys
HKR, "Parameters", "BreakOnEntry", 0x00010001, 0

; --------- Windows NT -----------------

[YouMark_DDI.NT]
CopyFiles=YouMark_Files_Driver
AddReg=YouMark_NT_AddReg

[YouMark_DDI.NT.Services]
Addservice = HelloWDM, 0x00000002, YouMark_AddService

[YouMark_AddService]
DisplayName = %SvcDesc%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %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="Zhangfan."
MfgName="Zhangfan Soft"
DeviceDesc="Hello World WDM!"
DeviceClassName="Zhangfan_Device"
SvcDesc="Zhangfan"
           

有關裡面的說明,以後再講。

有了inf後,就可以通過控制台->安裝硬體來安裝這個驅動。給出最後一個截圖,其他略。

Windows驅動開發WDM (2)- 一個簡單的WDM驅動程式

安裝成功後,可以在裝置管理裡面看到:

Windows驅動開發WDM (2)- 一個簡單的WDM驅動程式

這樣就安裝成功了。

搞了半天,這個驅動有啥用呢?當然我們可以寫一個使用者模式的測試程式。

調用驅動(使用者模式和核心模式通信)

寫個很簡單的測試例子:

// TestWDMDriver.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

#define DEVICE_NAME L"\\\\.\\HelloWDM"

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

	if (hDevice != INVALID_HANDLE_VALUE)
	{
		for(int i = 0; i < 10; i++)
		{
			DWORD RetBytes = 0;
			BOOL b = DeviceIoControl(hDevice, 0xAB, NULL, 0, NULL, 0, &RetBytes, NULL);
			printf("Test number %d, DeviceIoControl result: %d, byte: %d\n", i + 1, b, RetBytes);

			Sleep(1000);
		}

		CloseHandle(hDevice);
	}
	else
		printf("CreateFile failed, err: %x\n", GetLastError());

	return 0;
}

           

相當的簡單,通過CreateFile函數來打開HelloWDM驅動,通過裝置名字\\.\HelloWDM。這個名字對應驅動的AddDevice函數裡面設定的符号連結\\DosDevices\\HelloWDM。

這裡有必要講一下驅動的幾個名字概念:

1. 首先是裝置名稱,就是IoCreateDevice裡面用到的那個名字。這個名字隻能被核心模式的程式(如其他驅動)所識别。使用者模式的程式是不知道這個名字的。

2. 符号連結,驅動程式裡面可以為某個裝置設定符号連結,以\??\開頭(或者\DosDevices\),這樣使用者模式的程式就可以通過這個符号連結來通路這個裝置。

3. 使用者模式程式裡面的裝置名稱,以\\.\開頭

比如這個例子裡面核心模式的裝置名稱是"\Device\MyWDMDevice",符号連結是"\DosDevices\HelloWDM",然後使用者模式程式通過"\\.\HelloWDM"通路這個裝置。

當CreateFile成功打開這個裝置後,就可以操作這個裝置了。測試例子裡面簡單往這個裝置調用了10次DeviceIoControl函數(也就是發送一個IRP_MJ_DEVICE_CONTROL類型的IRP)。

運作測試程式,在debugview裡面可以看到:

Windows驅動開發WDM (2)- 一個簡單的WDM驅動程式

 哈哈,驅動程式成功地收到了來自測試程式(使用者模式)的資訊。

這裡隻是簡單列印一下資訊,

Major function code 是e(16進制),看一下WDM.H

#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE        0x01
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
           

确實0x0e對應的是IRP_MJ_DEVICE_CONTROL。

然後測試例子裡面發送了一個0xAB的控制碼給驅動,那麼驅動裡面通過stack->Parameters.DeviceIoControl.IoControlCode得到的控制碼也是0xab,看上面的debugview資訊。

這樣就大功告成了。首先我們寫了一個簡單的WDM驅動,然後安裝,之後在使用者模式裡面通路這個驅動。這也就是驅動的一般流程。

當然真正的驅動程式遠沒有這個例子簡單。我個人對于驅動大概也就是國小生的水準,需要繼續學習,研究。

我将完整代碼打包上傳了,有興趣可以下載下傳:http://download.csdn.net/detail/zj510/4794275

(用DDK編譯驅動,簡單調用build指令即可。用VS2008編譯使用者模式的測試程式)

繼續閱讀