在前一节中,我讲述了当WDM驱动程序被第一次装入时如何初始化。通常,一个驱动程序可以被多个设备利用。WDM驱动程序有一个特殊的 AddDevice函数,PnP管理器为每个设备实例调用该函数。该函数的原型如下:
NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{
}
DriverObject参数指向一个驱动程序对象,就是你在DriverEntry例程中初始化的那个驱动程序对象。 pdo参数指向设备堆栈底部的物理设备对象。
对于功能驱动程序,其AddDevice函数的基本职责是创建一个设备对象并把它连接到以 pdo为底的设备堆栈中。相关步骤如下:

1.     调用IoCreateDevice创建设备对象,并建立一个私有的设备扩展对象。

2.     寄存一个或多个设备接口,以便应用程序能知道设备的存在。另外,还可以给出设备名并创建符号连接。

3.     初始化设备扩展和设备对象的Flag成员。

4.     调用IoAttachDeviceToDeviceStack函数把新设备对象放到堆栈上。

下面我将详细解释这些步骤。
创建设备对象
调用 IoCreateDevice函数创建设备对象,例如:
PDEVICE_OBJECT fdo;
NTSTATUS status = IoCreateDevice(DriverObject,
                                sizeof(DEVICE_EXTENSION),
                                NULL,
                                FILE_DEVICE_UNKNOWN,
                                FILE_DEVICE_SECURE_OPEN,
                                FALSE,
                                &fdo);
第一个参数( DriverObject) 就是AddDevice的第一个参数。该参数用于在驱动程序和新设备对象之间建立连接,这样I/O管理器就可以向设备发送指定的IRP。
第二个参数是设备扩展结构的大小。正如我在本章前面讲到的,I/O管理器自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存。
第三个参数在本例中为 NULL。它可以是命名该设备对象的UNICODE_STRING串的地址。决定是否命名设备对象以及以什么名字命名还需要仔细考虑,我将在本节后面深入讨论这个问题。
第四个参数( FILE_DEVICE_UNKNOWN) 是表2-4中列出的设备类型。这个值可以被设备硬件键或类键中的超越值所替代,如果这两个键都含有该参数的超越值,那么硬件键中的超越值具有更高的优先权。对于属于某个已存在类的设备,必须在这些地方指定正确的值,因为驱动程序与外围系统的交互需要依靠这个值。另外,设备对象的默认安全设置也依靠这个设备类型值。

第五个参数(FILE_DEVICE_SECURE_OPEN) 为设备对象提供Characteristics标志(见表2-3)。这些标志主要关系到块存储设备(如软盘、CDROM、Jaz等等)。未公开标志位FILE_AUTOGENERATED_DEVICE_NAME仅用于内部使用,并不是DDK文档忘记提到该标志。这个参数同样也能被硬件键或类键中的对应值超越,如果两个值都存在,那么硬件键中的超越值具有更高的优先权。

第六个参数( FALSE) 指出设备是否是排斥的。通常,对于排斥设备,I/O管理器仅允许打开该设备的一个句柄。这个值同样也能被注册表中硬件键和类键中的值超越,如果两个超越值都存在,硬件键中的超越值具有更高的优先权。
注意

排斥属性仅关系到打开请求的目标是命名设备对象。如果你遵守Microsoft推荐的WDM驱动程序设计方针,没有为设备对象命名,那么打开请求将直接指向PDO。PDO通常不能被标记为排斥,因为总线驱动程序没有办法知道设备是否需要排斥特征。把PDO标为排斥的唯一的机会在注册表中,即设备硬件键或类键的Properties子键含有 Exclusive超越值。为了完全避免依赖排斥属性,你应该利用IRP_MJ_CREAT例程弹出任何有违规行为的打开请求。
第七个参数( &fdo) 是存放设备对象指针的地址,IoCreateDevice函数使用该变量保存刚创建设备对象的地址。
如果IoCreateDevice由于某种原因失败,则它返回一个错误代码,不改变fdo中的值。如果IoCreateDevice函数返回成功代码,那么它同时也设置了fdo指针。然后我们进行到下一步,初始化设备扩展,做与创建新设备对象相关的其它工作,如果在这之后又发现了错误,那么在返回前应先释放刚创建的设备对象并返回状态码。见下面例子代码:
NTSTATUS status = IoCreateDevice(...);
if (!NT_SUCCESS(status))
  return status;
...
if (<some other error discovered>)
{
  IoDeleteDevice(fdo);
  return status;
}
NTSTATUS状态代码和NT_SUCCESS宏的解释见下一章。
为设备命名

Windows NT使用对象管理器集中管理大量的内部数据结构,包括我们讨论过的驱动程序对象和设备对象。David Solomon在《Inside Windows NT, Second Edition (Microsoft Press, 1998)》的第三章“System Mechanisms”中给出了关于Windows NT对象管理器和命名空间的一个比较完整的阐述。对象都有名称,对象管理器用一个层次化的命名空间来管理这些名称。图2-16是DevView显示的顶层对象名。图中以文件夹形式显示的对象是目录对象,它可以包含子目录或常规对象,其它图标则代表正常对象。(从这一点上看,DevView与平台SDK中的WINOBJ工具相类似,但WINOBJ不能给出设备对象和驱动程序的相关信息)