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位清零。保证设备初始化完成,这一步是必须得。