A process that uses a COM object is known as
a COM client. Both UMDF drivers and the UMDF run-time function as COM
clients. UMDF drivers interact with UMDF run time by using UMDF-provided
COM objects. For example, the UMDF device object represents the device,
and drivers can use the object for tasks such as setting or retrieving
the device’s Plug and Play state.
The UMDF run time interacts with drivers
through the drive-provided COM-based callback objects. For example, a
driver can create one or more queue callback objects to handle I/O
requests. The UMDF run time uses those objects to pass request to the
driver.
After you get a pointer to an interface, you
can call the interface methods by using the same syntax that is used
for a pointer to a C++ method. For example, if pWdfRequest is a pointer to an IWDFloRequest interface, the following code is an example of how to invoke the interface’s Send method:
HRESULT hr;
hr = pWdfRequest- >Send( m_pIUsbTargetDevice,
WDR_REQUEST_SEND_OPTION_SYNCHRONOUS,
0);
The method’s return value is an HRESULT, a typical return type for COM methods. HRESULT is similar to the NTSTATUS type that Kernel Mode Drivers use as a return value and is used in much the same way. It is important not to think of HRESULT
as error values. Methods sometimes have multiple return values for
success as well as for failure. You can determine the result of calling a
method by comparing the returned HRESULT
to the list of possible values in the reference documentation. However,
be aware that these lists are not always complete. Use the
error-checking macros that are discussed shortly to ensure that you do
not miss a possible return value.
You can also test an HRESULT for simple success or failure. COM provides two macros for that purpose that work much like the NT_SUCCESS macro. For an HRESULT return value of hr:
Although NTSTATUS and HRESULT are similar, they are not interchangeable. Occasionally, information in the form of an NTSTATUS value must be returned as an HRESULT. In that case, you can use the HRESULT_FROM_NT macro to convert the NTSTATUS into an equivalent HRESULT. Do not use this macro for an NTSTATUS value of STATUS_SUCCESS. In that case, return the S_OK HRESULT value.
1. Obtaining an Interface on a UMDF Object
You can obtain an interface on a UMDF object in one of three ways:
The UMDF run time passes an interface pointer in to one of the driver’s callback methods.
The driver creates a new WDF object by calling a UMDF object creation method.
The driver calls IUnknown::QueryInterface to request a new interface from an existing WDF object.
You can also receive an Interface through a Driver
Method. The first case is the simplest. For example, when the UMDF run
time calls a driver’s IDriverEntry::OnDeviceAdd method, it passes a pointer to the device object’s IWDFDriver interface.
The following example shows this activity:
HRESULT CMyDriver::OnDeviceAdd(
__in IWDFDriver *FxWdfDriver,
__in IWDFDeviceInitialize *FxDeviceInit
)
{
// Install the driver in the device stack
}
You can then use FxWdfDriver to access the methods on the driver object’s IWDFDriver interface. Do not release FxWdfDriver when you are finished with it. The caller ensures that the object remains valid during the scope of the method call.
Another way to create a WDF object is by calling the
appropriate UMDF object creation method. For example, to create a
request object, call the UMDF device object’s IWDFDevice::CreateRequest method. If you look at the UMDF reference in the Windows Driver Kit (WDK), you will find syntax like that for IWDFDevice::CreateRequest:
HRESULT CreateRequest(
IN IUnknown* pCallbackInterface,
IN IWdfObject* pParentObject,
OUT IWDFIoRequest** ppRequest
);
ppRequest is an OUT parameter that provides an address at which the CreateRequest method can store a pointer to the newly created request object’s IWDFObject interface. The following procedure and sample show how to handle such parameters, by using a call to CreateRequest by the UMDF’s fx2_driver sample as an example.
We would declare a variable, pWdfRequest, to hold a pointer to IWDFloRequest.
Then we would pass a reference to pWdfRequest to CreateRequest as follows:
IWDFIoRequest *pWdfRequest = NULL;
...
hr = m_FxDevice- >CreateRequest ( NULL, NULL, &pWdfRequest);
When CreateRequest returns, pWdfRequest holds a pointer to an IWDFIoRequest interface. When the caller has finished with pWdfRequest, it should release the interface pointer by calling IUnknown::Release.
Another approach is to call QueryInterface
to request a new interface. Objects can expose more than one interface.
Sometimes, you have a pointer to one interface and need a pointer to
another interface on the same object. In that case, call IUnknown::QueryInterface to request the desired pointer. Pass QueryInterface the IID of the desired interface and the address of the interface pointer, and QueryInterface
returns the requested pointer. When the caller is finished with the
interface pointer, the caller should release it. The following is an
example:
VOID CMyDevice::StartTarget ( IWDFIoTarget * pTarget)
{
IWDFIoTargetStateManagement * pStateMgmt = NULL;
HRESULT hrQI =
pTarget->QueryInterface(IID_PPV_ARGS(&pStateMgmt));
...
}
This example requests an IWDFIoTargetStateManagement interface pointer from the UMDF’s I/O target object. It uses the IID_PPV_ARGS macro—declared in objbase.h—which takes an interface pointer and produces the correct arguments for QueryInterface.
QueryInterface belongs to the IUnknown interface. However, as shown earlier, there is no need to have an explicit pointer to an object’s IUnknown interface to call QueryInterface. All interfaces inherit from IUnknown, so you can use any interface to call QueryInterface.
2. Reference Counting
Unlike C++ objects, a client does not directly manage
the lifetime of a COM object. Instead, a COM object maintains a
reference count on itself. When a client creates a new object with an
object-creation method, the object has a reference count of 1. Each time
the client requests an additional interface on the object, the object
increments the reference count. When a client is finished with an
interface, it releases the interface pointer, which decrements the
reference count. When all the interface pointers on the object have been
released, the reference count is zero and the object destroys itself.
You must be extremely careful about handling
reference counts when you use or implement COM objects. Although clients
do not explicitly destroy COM objects, there is no garbage collection
to take care of the problem automatically as there is with managed code.
A common mistake
is to fail to release an interface. In that case, the reference count
never goes to zero and the object remains in memory indefinitely.
Conversely, releasing the interface pointer too many times causes the
object to be destroyed prematurely, which can cause a crash. Failure to
correctly manage reference counts is a common cause of memory leaks in
COM-based applications, along with a variety of other problems. Even
worse, bugs that are caused by mismanaged reference counts can be very
difficult to locate.
The following are some basic rules for reference counting:
Release any interface pointer that is passed to you as an OUT parameter when you are finished with it by calling IUnknown::Release. Do not release pointers that are passed as IN parameters. A common practice to ensure that all interface pointers are properly released is to initialize all pointers to NULL. Then set them to NULL again when they are released. That convention allows you to test all the interface pointers in your cleanup code; any non-NULL pointers are still valid and should be released.
The
reference count is usually incremented for you. The main exception is
when you make a copy of an interface pointer. In that case, call IUnknown::AddRef to explicitly increment the object’s reference count. You must then release the pointer when you are finished.
When you discover that the driver has reference counting problems, do not attempt to fix them by simply adding calls to AddRef or Release.
Make sure that the driver is acquiring and releasing references
according to the rules. Otherwise, you may find, for example, that the Release calls that you added to solve a memory leak occasionally deletes the object prematurely and instead causes a crash.
As with QueryInterface, you do not need a pointer to the object’s IUnknown interface to call AddRef or Release. You can call these methods from any of the object’s interfaces.