AddDevice例程是WDM驱动所独有的,在NT驱动中没有该例程。在DriverEntry中,需要设置AddDevice例程的函数地址。设置的方式是驱动对象中有个 DriverExtension 子域,DriverExtension中有个AddDevice子域,将该子域指向AddDevice例程的函数地址。
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
和DriverEntry不同,AddDevice例程的名字可以任意命名,程序员可以用更有意义的名字作为这个函数的名字。在HelloWDM例子中,使用的名称就是HelloWDMAddDevice。
#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PAGED_CODE();
KdPrint(("Enter HelloWDMAddDevice\n"));
NTSTATUS status;
PDEVICE_OBJECT fdo;
UNICODE_STRING devName;
// 初始化UNICODE字符串
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;
// 将FDO附加在PDO上
pdx->fdo = fdo;
pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
UNICODE_STRING symLinkName;
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;
fdo->Flags &= ~DO_DEVICE_INITIALIZING;
KdPrint(("Leave HelloWDMAddDevice\n"));
return STATUS_SUCCESS;
}
从上述代码中可以看出,AddDevice例程类似于NT驱动中DriverEntry创建设备对象的相关操作,但是略有不同。AddDevice函数有两个输入参数,一个是驱动对象DriverObject,另一个是设备对象PhysicalDeviceObject。驱动对象是I/O管理器创建的驱动对象。设备对象PhysicalDeviceObject就是底层驱动创建的PDO设备对象。传进该参数的目的就是将FDO附加在PDO之上。
在AddDevice中可以分为以下几个步骤:
1. 在AddDevice通过IoCreateDevice等函数,创建了设备对象,该设备对象就是FDO,即功能驱动设备对象。和NT驱动一样,可以设置驱动对象的设备名称,也可以不设置,如果不设置设备名称,I/O管理会自动以一个数字作为该设备对象的名称。
2. 创建完FDO后,需要将FDO的地址保存下来,以便以后使用。保存的位置是在设备扩展中,在驱动程序中应该尽量避免使用全局变量,而使用设备扩展。如果该电脑中存在多个同类设备,例如,插入两个型号相同的网卡,操作系统会两次两用AddDevice例程。每个AddDevice例程创建各自的FDO,分别记录在各自的设备扩展中。
3. 驱动程序将创建的FDO附加在PDO上,附加这个动作是依靠IoAttachDeviceToDeviceStack函数实现的。IoAttachDeviceToDeviceStack的声明如下:
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
PDEVICE_OBJECT SourceDevice,
PDEVICE_OBJECT TargetDevice
);
* SourceDevice:要附加在别的设备之上的设备。将FDO附加在PDO之上时,这个填写的是FDO的地址。
* TargetDevice:被附加的设备。将FDO附加在PDO之上时,这个填写的是PDO的地址,当FDO想附加在PDO上时,有时会在PDO和FDO上附加过滤驱动。此时,FDO其实是附加在过滤设备上,而过滤设备附加在PDO上。
* 返回值:附加以后,返回附加设备的下层设备。如果中间没有过滤驱动的话,返回值就是PDO,如果中间有过滤驱动,返回的是过滤驱动。
当FDO附加到PDO上时,PDO会通过AttachedDevice子域知道它上面的设备是FDO(或者是过滤驱动)。但是FDO却不知道自己的下层是什么设备。解决的办法是,通过设备扩展记录FDO下层的设备。下面是HelloWDM的设备扩展的定义:
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
PDEVICE_OBJECT NextStackDevice;
UNICODE_STRING ustrDeviceName; // 设备名
UNICODE_STRING ustrSymLinkName; // 符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
我们在自己编写驱动程序时,可以根据自己的需要定制自己的设备扩展。子域fdo是为了保存FDO的地址,以备后用。子域NextStackDevice是为了定位设备的下一层设备。
在附加操作完成,需要设定符号链接,以便用户应用程序可以访问该设备。
4. 设置fdo的flags子域。DO_BUFFERED_IO是定义设备为“缓冲内存设备”。另外~DO_DEVICE_INITIALIZING,是将Flag上的DO_DEVICE_INITIALIZING位清零。保证设备初始化完成,这一步是必须得。