The source file Comsup.cpp supplies code that is required to support COM. It implements methods for the IUnknown and IClassFactory
interfaces. This section briefly describes what these methods do, but
does not show any of the sample code. You can simply copy the Comsup.cpp and Comsup.h files for use in your own drivers, typically without any changes.
1. IUnknown Methods
CUnknown is the base class from which all other classes derive, and it supports the IUnknown interface. Every UMDF driver must implement this class with a constructor method and the IUnknown interface, which includes the AddRef, QueryInterface, QueryIUnknown, and Release methods. Table 1 summarizes the IUnknown methods.
Table 1. IUnknown Method Names
Method Name | Description |
---|
CUnknown | Constructor, which initializes the reference count for this instance of the CUnknown class to 1. |
QueryInterface | Returns a pointer to the IUnknown interface for the object. |
QueryIUnknown | Public helper method that casts a CUnknown pointer to an IUnknown pointer. |
AddRef | Increments the reference count for the object. |
Release | Decrements the object’s reference count and deletes the object if the reference count reaches zero. |
2. IClassFactory Interface
The CClassFactory class object implements the IClassFactory
interface. The framework invokes methods in this interface to create an
instance of the driver callback class. The driver callback class
instance is the only callback object that the framework creates; the
driver itself creates all other callback objects in response to calls
from the framework. Table 2 summarizes the methods in this interface.
Table 2. IClassFactory Methods
Method | Description |
---|
QueryInterface | Returns a pointer to the requested interface. |
QueryIClassFactory | Public helper method that casts a CClassFactory pointer to an IClassFactory pointer; essentially similar to the IQueryIUnknown method in the IUnknown interface. |
CreateInstance | Creates an instance of the driver callback class and returns a pointer to a requested interface for that class. |
LockServer | Maintains a lock count that indicates whether the driver DLL should remain in memory. |
3. Driver CallBack Object
When UMDF gets a pointer to the IClassFactory interface, it calls the CreateInstance method in that interface to create an instance of an object. That method, in turn, calls the CMyDriver::CreateInstance method, which creates and initializes the driver callback object. In general, any CreateInstance method is a factory method that creates an object.
CMyDriver::CreateInstance is defined in the source file Driver.cpp and is straightforward, as the following shows:
HRESULT
CMyDriver::CreateInstance(
__out PCMyDriver *Driver
)
/*++
Routine Description:
This static method is invoked to create and initialize a new
instance of the driver class. The caller should arrange for
the object to be released when it is no longer in use.
Arguments:
Driver - a location to store a referenced pointer to
the new instance
Return Value:
S_OK if successful, or error otherwise
--*/
{
PCMyDriver driver;
HRESULT hr;
//
// Allocate the callback object
//
driver = new CMyDriver();
if (NULL == driver)
{
return E_OUTOFMEMORY;
}
//
// Initialize the callback object
//
hr = driver->Initialize();
if (SUCCEEDED (hr))
{
//
// Store a pointer to the new, initialized object in
// the output parameter.
//
*Driver = driver;
}
else
{
//
// Release the reference on the driver object so that
// it will delete itself
//
driver->Release();
}
return hr;
}
This
method allocates and creates an instance of the driver callback object,
and then calls the initialize method to initialize the object. The Skeleton driver object requires no initialization, so the Initialize method is a stub (and so is not shown here). CreateInstance returns a pointer to the new driver callback object and releases its reference on this object before returning.
Every UMDF driver must implement the IDriverEntry
interface on the driver callback object. This interface supports
methods to initialize the driver, perform tasks when one of the driver’s
devices is added to the system, and prepare the driver for unloading,
just before DllMain.DetachProcess is called. The driver.cpp file contains code that implements IDriverEntry.
IDriverEntry defines three methods: OnDeviceAdd, OnInitialize, and OnDeInitialize. In the Skeleton driver, the OnInitialize and OnDeInitialize methods are stubs.
When one of the driver’s devices is added, UMDF calls the OnDevice method, passing as parameters pointers to the IWdfDriver and IWdfDeviceInitialize interfaces, which the framework implements. The Skeleton driver does not support physical hardware, so its OnDeviceAdd method is minimal:
HRESULT
CMyDriver::OnDeviceAdd{
__in IWDFDriver *FxWdDriver,
__in IWDFDeviceInitialize *FxDeviceInit
}
/*++
Routine Description:
The FX invokes this method to install our driver on a device
stack. This method creates a device callback object, then
calls the Fx to create an Fx device object and associate the
new callback object with it.
Arguments:
FxWdfDriver - the Fx driver object.
FxDeviceInit - the initialization information for the
device.
Return Value:
Status
--*/
{
HRESULT hr;
PCMyDevice device = NULL;
//
// TODO: Here is where to do any per device initialization
// (reading settings from the registry, for example) that is
// required before you create the device callback object.
// You can leave initialization of the device callback
// object itself to the device event handler.
//
//
// Create a new instance of our device callback object
//
hr = CMyDevice::CreateInstance(FxWdfDriver, FxDeviceInit,
&device);
//
// TODO: Change any per device settings that the object
// exposes before you call Configure to complete its
// initialization.
//
//
// If that succeeded then call the device's construct
// method. The construct method can create queues or
// other structures that are required for the device object.
if (S_OK == hr)
{
hr = device->Configure();
}
//
// Release the reference on the device callback object
// now that it's associated with an fx device object.
//
if (NULL != device)
{
device->Release();
}
return hr;
}
The Skeleton driver’s OnDeviceAdd method calls the CreateInstance method on the CMyDevice class to instantiate the device callback object. It passes the pointers to the IWdfDeviceInitialize and IWdfDriver interfaces so that CreateInstance can use these UMDF defined interfaces to create and initialize the device object.
By convention, a CreateInstance method in the sample represents a factory for building objects of a particular type.
4. Device CallBack Object
The device callback object represents the device in the driver. The driver creates an instance of this object when its IDriverEntry::OnDeviceAdd method is called. The driver implements the CreateInstance, Initialize, Configure, and QueryInterface methods for the device callback object.
The code for the device callback object for the Skeleton driver is in the Device.cpp. This module includes the header files Internal.h, which contains driver-specific internal definitions, and Device.tmh, which defines tracing information for Event Tracing for Windows (ETW).
4.1. CreateInstance Method
A driver’s IDriverEntry::OnDeviceAdd method calls IDeviceObject::CreateInstance
to create an instance of the device callback object. This method simply
allocates and initializes an instance of the device callback object as
follows:
HRESULT
CMyDevice::CreateInstance(
__in IWDFDriver *FxDriver,
__in IWDFDeviceInitialize *FxDeviceInit,
__out PCMyDevice *Device
)
/*++
Routine Description:
This method creates and initializes an instance of the
skeleton Driver's device callback object.
Arguments:
FxDeviceInit - the settings for the device.
Device - a location to store the referenced pointed to the
device object.
Return Value:
Status
--*/
{
PCMyDevice device;
HRESULT hr;
//
// Allocate a new instance of the device class.
//
device = new CMyDevice();
if (NULL == device)
{
Return E_OUTOFMEMORY;
}
//
// Initialize the instance.
//
hr = device->Initialize(FxDriver, FxDeviceInit);
if (S_OK == hr)
{
*Device = device;
}
else
{
device->Release();
}
return hr;
}
When UMDF calls the OnDeviceAdd method, it passes a pointer to the IWdfDriver interface and a pointer to the IWdfDeviceInitialize
interface. These interfaces provide methods through which the driver
can initialize per-device-object settings and create a device callback
object. OnDeviceAdd passes these pointers to CreateInstance, which in turn passes them as parameters to the Initialize method to initialize the instance.
4.2. Initialize Method
The Initialize
method of the device callback object does exactly what its name implies:
It initializes the callback object. It also calls the framework to
create the framework’s device object.
The initialize method receives a handle to the framework’s IWdfDeviceInitialize interface and stores it in FxDeviceInit.
It uses this handle to call methods on that interface to initialize
certain device characteristics that must be set before the framework’s
device object is created. Such characteristics include the
synchronization (locking) model and the Plug and Play features. They
also indicate whether the driver is a filter driver, whether the driver
controls device power policy, and whether the framework should forward
or fail certain request types. The driver must set these values before
creating the framework’s device object because they determine which
callbacks the framework initializes for the device object. The following
code shows how to set the needed values:
HRESULT
CMyDevice::Initialize(
__in IWDFDriver *FxDriver,
__in IWDFDeviceInitialize *FxDeviceInit
)
/*++
Routine Description:
This method initializes the device callback object and
creates the partner device object.
The method should perform any device specific configuration
that:
Could fail (these can't be done in the constructor)
must be done before the partner object is created-or-
can be done after the partner object is created and
isn't influenced, by any device level parameters that the
parent (the driver in this case) might set.
Arguments:
FxDeviceInit - the settings for this device.
Return Value:
status.
--*/
{
IWDFDevice *fxDevice;
HRESULT hr;
//
// Configure things like the locking model before we
// create our partner device.
//
//
// TODO: Set her locking mode. The skeleton uses device level
// locking, but you can choose "none" as well.
//
FxDeviceInit->SetLockingConstraint (WdfDeviceLevel);
//
// TODO: If you're writing a filter driver indicate that
// here.
//
// FxDeviceInit->SetFilter();
//
//
// TODO: Any per-device initialization which must be done
// before creating the partner object.
//
//
// Create a new FX device object and assign the new
// callback object to handle any device level events that
// occur.
//
//
// QueryIUnknown references the IUnknown interface that it
// returns (which is the same as referencing the device). We
// pass that to CreateDevice, which takes its own reference
// if everything works.
//
{
IUnknown *unknown = this->QueryIUnknown();
hr = FxDriver->CreateDevice(FxDeviceInit, unknown,
&fxDevice);
unknown->Release();
}
//
// If that succeeded, then set our FxDevice member variable.
//
if (S_OK == hr)
{
m_FxDevice = fxDevice;
//
// Drop the reference we got from CreateDevice. Since
// this object is partnered with the framework object
// they have the same lifespan. There is no need for an
// additional reference.
//
}
return hr;
}
The Initialize method first sets the locking model for the driver by calling the SetLockingConstraint method of the IWdfDeviceInitialize
interface. The locking model determines whether the framework calls the
driver’s callback methods concurrently on a per-device-object level or
not at all. The Skeleton driver sets WdfDeviceLevel,
which means that the framework synchronizes calls to methods at the
device object level or lower. Therefore, the driver does not require
code to synchronize access to shared data in such methods.
Device-level locking applies to methods on the IWdfIoQueue interface, the IFileCallbackCleanup interface, and the IFileCallbackClose interface. The IWdfIoQueue interface is implemented by the I/O queue object, and the IFileCallbackCleanup and IFileCallbackClose interfaces are implemented by the device object.
The Skeleton does
not support physical hardware, so it does not set any Plug and Play
characteristics. If it supported an actual Plug and Play device, it
might also have to specify whether the device is ejectable, lockable,
and other similar settings.
After the driver has set the device characteristics, it can call UMDF to create the framework’s device object. The IWdfDriver::CreateDevice method takes a pointer to the IWdfDeviceInitialize
interface that was passed to the driver, a pointer to the driver’s
device callback object, and a location in which to return the handle to
the created framework device object. To get a pointer to the device
callback object, the driver calls QueryIUnknown on the current interface. It then passes this pointer when it calls CreateDevice. Calling QueryIUnknown adds a reference on the IUnknown interface it returns—in this case, the driver’s callback object interface. After the CreateDevice method returns, the driver releases this reference.
If UMDF successfully creates the framework device
object, the driver initializes the variable m_FxDevice to hold the
pointer to the returned interface. It then calls the Release method to release the reference that the CreateDevice method added on the returned interface. The m_FxDevice interface has the same lifetime as the framework’s IWdfDevice interface, so this reference is not required to ensure that the interface persists for the driver.
4.3. Configure Method
The Configure method handles tasks that are related to configuration after the framework and device callback objects have been created. The Skeleton driver’s OnDeviceAdd callback invokes the Configure method after CreateInstance has successfully returned.
In the Skeleton driver, Configure is a stub. In a driver that handles I/O requests, this method would create and configure I/O queues and queue callback objects.
4.4. QueryInterface Method
The QueryInterface method returns a pointer to any of the device callback object’s interfaces. It takes the interfaceId as an input parameter and returns a pointer to the interface.
The Skeleton
driver does not implement any of the event callback interfaces for the
device object because it does not support actual hardware. Therefore, it
simply returns the pointer to the IUnknown interface of the base class CUnknown, as follows:
return CUnknown::QueryInterface(InterfaceId, object);
In a driver that supports actual hardware, this method should validate the input interfaceId and return a pointer to the requested interface.