A collection object is a linked list of KMDF objects. The objects in the collection can all be of the same type or can be of various types. An object can be in more than one collection.
Collections are useful when a driver must keep track of several related objects, particularly if the objects do not require service in a predictable order. For example:
A driver that breaks a request to read or write a large amount of data into several smaller requests creates a collection that contains all of the smaller I/O requests for a large request.
A driver that handles requests for more than one device creates a collection of device objects.
A driver for a device that supports message-signaled interrupts creates a collection of interrupt objects, where each object represents one message.
Collection objects have several advantages over simple driver-implemented linked lists:
Every time a driver adds an item to the collection, KMDF increments the item's reference count, so that the item's handle remains valid as long as the item is in the collection.
KMDF implements all the list-tracking and management code, so that drivers are not required to store a link or index in each object's context area.
However, collection objects have one significant disadvantage compared with linked lists: adding an object to a collection can fail if insufficient memory is available. Your driver should always check for such errors. Generally, you should avoid using collection objects in critical paths that cannot tolerate failure.
The KMDF collection object supports methods that add and delete items from the collection, return an item from the collection, and return the number of items in the collection. Table 12-6 lists the collection object methods.
Method | Description |
---|---|
WdfCollectionAdd | Adds an object to a collection. |
WdfCollectionCreate | Creates a collection object. |
WdfCollectionGetCount | Returns the number of objects in the collection. |
WdfCollectionGetFirstItem | Returns the handle of the first object in the collection. |
WdfCollectionGetItem | Returns the handle of an object in the collection, given the index to the object in the collection. |
WdfCollectionGetLastItem | Returns a handle for the last object in the collection. |
WdfCollectionRemove | Removes an object from the collection, based on the object's handle. |
WdfCollectionRemoveItem | Removes an object from the collection, based on the object's index in the collection. |
KMDF does not provide any synchronization for the collection object, so if more than one driver function can simultaneously access the collection, the driver must create and acquire its own lock for the collection object.
The objects in the collection are indexed starting at zero, and a driver can retrieve or remove an object by specifying the index. A driver can also remove an object by supplying a handle to the object. When the framework removes an object from the collection, the framework adjusts the indices. For example, if a driver removes the nth object from the collection, object n+1 becomes object n and so forth.
The framework increments the object's reference count when a driver adds the object to a collection and decrements the reference count when a driver removes the object from the collection.
The Toastmon driver uses a collection to store information about the Toaster devices attached to the system. Each time a Toaster device interface arrives, the driver creates a corresponding I/O target object and adds it to a collection of Toaster I/O targets. Each time a Toaster device interface is removed, the driver removes it from the collection. More than one driver function accesses the collection, so the driver creates a WDF wait lock to serialize access. The driver never accesses the collection at IRQL DISPATCH_LEVEL or greater, so a spin lock is not required.
Listing 12-9 shows how the Toastmon driver creates the collection and the lock in the Toaster\Toastmon\Toastmon.c file.
Listing 12-9: Creating a collection object in a KMDF driver
WDF_OBJECT_ATTRIBUTES attributes; NTSTATUS status = STATUS_SUCCESS; WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.ParentObject = device; status = WdfCollectionCreate(&attributes, &deviceExtension->TargetDeviceCollection); if (!NT_SUCCESS(status)) { . . . //Error handling code omitted } WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.ParentObject = device; status = WdfWaitLockCreate(&attributes, &deviceExtension->TargetDeviceCollectionLock); if (!NT_SUCCESS(status)) { . . . //Error handling code omitted }
In the listing, the device variable contains a handle to the WDFDEVICE object, and deviceExtension contains a pointer to the context area for the device object. The context area is defined as follows:
typedef struct _DEVICE_EXTENSION { WDFDEVICE WdfDevice; WDFIOTARGET ToasterTarget; PVOID NotificationHandle; // Interface notification handle WDFCOLLECTION TargetDeviceCollection; WDFWAITLOCK TargetDeviceCollectionLock; PVOID WMIDeviceArrivalNotificationObject; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Before the driver creates the collection object, it initializes an object attributes structure and sets the device object as the collection's parent. The call to WdfCollectionCreate creates the collection object and returns the object's handle in the device object context area at TargetDeviceCollection.
To create the lock, the driver proceeds similarly. It initializes the object attributes structure, sets the Parent field to the device object, and calls WdfWaitLockCreate to create the wait lock object. The driver stores the handle to the lock in the device object context area at TargetDeviceCollectionLock.
Chapter 10, "Synchronization," provides more information on wait locks.
When a new device interface arrives, the Toastmon driver creates an I/O target object to represent the interface. As Listing 12-10 shows, the driver adds the I/O target object to the collection.
Listing 12-10: Adding an object to a collection
WdfWaitLockAcquire(deviceExtension->TargetDeviceCollectionLock, NULL); status = WdfCollectionAdd(deviceExtension->TargetDeviceCollection, ioTarget); if (!NT_SUCCESS(status)) { . . . //Error handling code omitted } WdfWaitLockRelease(deviceExtension->TargetDeviceCollectionLock);
The driver acquires the lock that protects the collection by calling WdfWaitLockAcquire, passing NULL for the time-out value to indicate that the driver will wait indefinitely for the lock to become available. The driver then adds the object to the collection by calling WdfCollectionAdd, passing a handle to the collection and a handle to the object. When the driver has finished accessing the collection, it calls WdfWaitLockRelease to release the lock.
Listing 12-11 shows how the driver removes an object from the collection.
Listing 12-11: Removing an object from a collection
WdfWaitLockAcquire(deviceExtension->TargetDeviceCollectionLock, NULL); WdfCollectionRemove(deviceExtension->TargetDeviceCollection, IoTarget); WdfWaitLockRelease(deviceExtension->TargetDeviceCollectionLock);
Removal is a simple matter of acquiring the lock, calling WdfCollectionRemove, and releasing the lock, as the listing shows. A driver can remove an object from a collection by specifying the object's handle, as in the example, or by specifying the object's index in the collection.
The driver does not delete the collection, because the framework automatically deletes the collection when it deletes the device object, which is the parent of the collection object.