The object context area is a driver-defined storage area for data that the driver uses with a particular object. UMDF drivers and KMDF drivers tend to use object context areas in different ways:
A UMDF driver typically stores context data in a driver callback object, but can create a context area in a framework object if necessary.
KMDF drivers do not create callback objects and therefore typically create and use a context area in a framework object.
UMDF drivers can store context data that pertains to a particular object in either of two ways:
In data members of the callback object.
In a separate context area that the driver defines and assigns to the framework object.
The best location for the context data depends on the relationship between the framework object and the callback object. If a one-to-one correspondence between the framework object and the callback object exists-that is, if the callback object serves only one framework object-the best place to store the context data is usually the callback object itself. For example, if your driver creates a unique queue callback object for each I/O queue, you can store the queue-specific context data in a data member of the queue callback object instead of creating a context area in the framework queue object.
If a callback object serves more than one framework object-as for I/O requests and file objects-it is better to store the object-specific data with each individual framework object. I/O requests and file objects are the most common example. A driver does not typically create a dedicated callback object for each I/O request that it receives from the framework, because doing so would result in many transient objects and waste memory. Instead, the driver should store any context data with the framework request object.
You can use the callback object and the context area in a couple of ways.
For a structural object like a device or a queue, it's better to have an actual callback object for each framework object. You can maintain pointers between the callback objects if a device method needs to access a particular queue.
For something transient like a request, it might make more sense to implement the request callback interfaces on an existing callback object, such as the queue or the device callback object. This way you don't need to allocate a callback object for each request that comes in.
If you need to store a small amount of context data for each request, but don't want to implement a new COM class for it, you can use a mix of callback and context. Implement the callback interfaces on the queue or the device callback object as before, but allocate a small area of memory as the framework request object's context area. The context area can maintain any information you want, and the device or queue handles the COM aspects of the callback interfaces. Just remember that you also must implement an IObjectCleanup::OnCleanup callback for the framework request object so that you can free the context structure.
Even if you decide to create a request callback object, you might still find a context area useful. Say you allocate the callback object and then forward the request to a manual queue for later processing. When you retrieve the request from the manual queue, you'll probably need to find the callback object, and setting the context area to the callback object pointer lets you do this quite easily. In this case, the context and the callback are the same, so you don't necessarily need an OnCleanup callback; regular COM reference counting will free the context area for you.
-Peter Wieland, Windows Driver Foundation Team, Microsoft
To store context data that is specific to a driver-created callback object, a UMDF driver can simply declare data members of the object class. In this way, the callback object provides its own context memory. For example, Listing 5-5 shows an excerpt from the Fx2_Driver sample's Device.h header file that declares private data members for the device callback object.
Listing 5-5: Storing context data in a callback object
class CMyDevice : public CUnknown, public IPnpCallbackHardware, public IPnpCallback, public IRequestCallbackRequestCompletion { // Private data members. private: IWDFDevice * m_FxDevice; PCMyReadWriteQueue m_ReadWriteQueue; PCMyControlQueue m_ControlQueue; IWDFUsbTargetDevice * m_pIUsbTargetDevice; IWDFUsbInterface * m_pIUsbInterface; IWDFUsbTargetPipe * m_pIUsbInputPipe; IWDFUsbTargetPipe * m_pIUsbOutputPipe; IWDFUsbTargetPipe * m_pIUsbInterruptPipe; UCHAR m_Speed; SWITCH_STATE m_SwitchState; HRESULT m_InterruptReadProblem; SWITCH_STATE m_SwitchStateBuffer; IWDFIoQueue * m_SwitchChangeQueue; }
The private data members store information that the driver uses with the device callback object, including a pointer to the framework's IWDFDevice interface, pointers to the device's I/O queues, and pointers to the framework's interfaces for the USB I/O target, for the USB interface, and for the USB pipe objects, plus assorted state variables.
A UMDF driver can associate object-specific data with a framework object, such as an I/O request or file object, by creating a context area and associating it with the object. The driver can assign the context area at any time, but typically does so immediately after creating the object. In UMDF, a framework object can have only one context area.
The context area is simply a driver-allocated storage area. To declare a context area, the driver simply creates a structure that can hold the required data. To assign a context area, the driver calls IWDFObject::AssignContext on the object, passing a pointer to an IObjectCleanup interface and a pointer to the context area.
The IObjectCleanup interface supports the OnCleanup method, which frees the context area and releases any extra references that the driver took on the object. The framework calls OnCleanup immediately before it deletes the object.
In the example shown in Listing 5-6, the driver creates an object of the driver-defined type Context and assigns this as the context area for a file object. This example is from the WpdHelloWorldDriver and appears in the Queue.cpp file.
Listing 5-6: Creating a context area in a UMDF object
Context* pClientContext = new Context(); if(pClientContext != NULL) { hr = pFileObject->AssignContext(this, (void*)pClientContext); if(FAILED(hr)) { pClientContext->Release(); pClientContext = NULL; } }
The context area is not by default a COM object, although a driver can use a COM object, as the example does. If the driver uses a COM object, the driver must manage the reference count on the object.
In the listing, the driver creates a new context object and calls AssignContext to assign it to the file object. The driver passes a pointer to the driver-implemented IObjectCleanup interface for the context area and a pointer to the context itself. If the AssignContext method fails, the driver releases its reference on the context area and sets the context pointer to NULL. Listing 5-7 shows the OnCleanup method for the context area.
Listing 5-7: Sample OnCleanup method for a UMDF context area
STDMETHODIMP_ (void) CQueue::OnCleanup( IWDFObject* pWdfObject ) { HRESULT hr = S_OK; Context* pClientContext = NULL; hr = pWdfObject->RetrieveContext((void**)&pClientContext); if((hr == S_OK) && (pClientContext != NULL)) { pClientContext->Release(); pClientContext = NULL; } }
The OnCleanup method calls IWDFObject::RetrieveContext to get a pointer to the context area. In this sample, the context area is a COM object, so the driver releases its reference on the context area and then sets the pointer to NULL.
A KMDF object can have one or more object context areas. The framework allocates the context areas from the nonpaged pool and zeroes them out at initialization. The context areas are considered part of the object.
For device objects and other driver-created objects, the driver typically assigns a context area when it creates the object. For a device object, the object context area is the equivalent of the WDM device extension. In fact, the DeviceExtension field of the WDM DEVICE_OBJECT structure points to the first context area that is assigned to the WDF device object.
To assign a context area to a framework-created object after it has been created, or to assign additional context areas to a driver-created object, the driver calls the WdfObjectAllocateContext method. The driver passes a handle for the object to which to assign the context area. The handle must represent a valid object. If the object is in the process of being deleted, the call fails. If an object has more than one context area, each context area must be of a different type.
The driver defines the size and type of each context area in a header file and records this information in an object attributes structure. The attributes structure is an input parameter to the object creation method or to WdfObjectAllocateContext. The framework provides macros to associate a type and a name with the context area and to create a named accessor function that returns a pointer to the context area. The driver must use an accessor method to get a pointer to the context area because the context area is part of the opaque object. Each context area has its own accessor method.
If you're familiar with WDM, the context area design in WDF might seem unnecessarily complicated. However, context areas provide flexibility in attaching information to I/O requests as they flow through the driver and enable different libraries to have their own separate context for an object. For example, an IEEE 1394 library could track a WDFDEVICE object at the same time that the device's function driver tracks it, but with separate contexts.
The context area is accessible only to the component that created it. Within a driver, the context area enables data abstraction and encapsulation. If the driver uses a request for several different tasks, the request object can have a separate context area for each task. Functions that are related to a specific task can access their own contexts and do not require any information about the existence or contents of any other contexts.
-Doron Holan, Windows Driver Foundation Team, Microsoft
When KMDF deletes the object, it also deletes the context areas. A driver cannot delete a context area dynamically. The context areas persist until the object is deleted.
To set up a context area:
Declare the type of the context area.
Initialize an object attributes structure with information about the context area.
Assign the context area to the object.
To declare the context area type, use the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro in a header file.
The context-type macros associate a type with the context area and create a named accessor method that returns a pointer to the context area. WDF_DECLARE_CONTEXT_TYPE_WITH_NAME assigns a driver-specified name to the accessor method.
For example, the code snippet in Listing 5-8-from the Nonpnp.h file in the Nonpnp sample-defines the context type for an I/O request object.
Listing 5-8: Declaring the context type for a KMDF object
typedef struct _REQUEST_CONTEXT { WDFMEMORY InputMemoryBuffer; WDFMEMORY OutputMemoryBuffer; } REQUEST_CONTEXT, *PREQUEST_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, GetRequestContext)
The listing declares the context type REQUEST_CONTEXT and names its accessor function GetRequestContext.
The driver fills the type of the context area into an object attributes structure by using the WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE or WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE macro.
The WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE macro records information about the context area in a previously initialized attribute structure, which the driver later supplies when it creates the object.
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE combines the actions of WDF_OBJECT_ATTRIBUTES_INIT and WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE.
That is, it initializes the attribute structure with settings for other attributes in addition to information about the context.
The example in Listing 5-9 initializes an attribute structure with information about the context area defined in step 1.
Listing 5-9: Initializing an attribute structure with context area information
WDF_OBJECT_ATTRIBUTES attributes; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT);
The listing initializes the attributes structure with information about the REQUEST_CONTEXT type.
A KMDF driver can associate the context with a new object or with an existing object. To associate the context area with a new object, the driver passes the initialized attributes structure in the call to the object creation method.
To associate a context with an existing object, the driver calls WdfObjectAllocateContext, which takes three parameters: a handle to an object, a pointer to the initialized attributes structure, and a location in which the method returns a pointer to the allocated context area.
The example in Listing 5-10 allocates the context area that was set up in the previous two steps and assigns it to the Request object.
Listing 5-10: Assigning a context area to an existing KMDF object
status = WdfObjectAllocateContext(Request, &attributes, &reqContext);
When the driver deletes the request object, the framework deletes the context area as well.