Windows驱动开发WDM (2)- 一个简单的WDM驱动程序

时间:2021-08-13 19:50:19

这个例子是从《windows驱动开发技术详解》的光盘上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编译用户模式的测试程序)