In general, the UMDF drivers are programmed
using C++, and the COM objects are developed and also written in C++. It
is good to have an understanding of class structure such as the struct
and class keywords, public and private members, static methods,
constructors, destructors, and pure abstract classes. Also, you should
understand object creation, which includes base and derived classes,
multiple inheritance, and pure virtual methods.
1. COM Fundamentals
Let’s look at some of the fundamental aspects of COM to get us started:
IUnknown
is the core COM interface. All COM interfaces derive from this
interface. Every COM object exposes this interface and it is essential
to the object’s operation.
One of the
significant differences between objects in COM and other Object Oriented
Programming (OOP) models is that there are no fundamental object
pointers in COM. COM exposes interfaces that are groups of related
methods. Objects typically expose at least two and sometimes many
interfaces. Thus, when you obtain a COM object, you are given a pointer
to one of the object’s interfaces not the object itself.
Globally Unique Identifiers
(GUIDs) are used by COM to uniquely identify COM interfaces. Some COM
objects have GUID identifiers, which are referred to as CLSIDs. GUIDs
are referred to as IIDs. We can request an interface pointer using these
IIDs. COM uses GUIDs for two primary purposes:
Interface ID (IID)—
An IID is a GUID that uniquely identifies a particular COM interface.
The interface always has the same IID, regardless of which object
exposes it.
Class ID (CLSID)—
A CLSID is a GUID that identifies a particular COM object. CLSIDs are
required for COM objects that are created by a class factory, but
optional for objects that are created in other ways. With UMDF, only the
driver callback object has a class factory or a CLSID.
To simplify using GUIDs, an associated header file usually defines friendly names that conventionally have a prefix of either IID_ or CLSID_ followed by the descriptive name. For example, the friendly name for the GUID that is associated with IDriverEntry is IID_IDriveEntry. For convenience, the UMFD documentation usually refers to interfaces by the name used in their implementation, such as IDriverEntry, rather than the IID.
Any
of the methods on an interface can be used with an interface pointer.
If you want access to a method on another interface you must obtain
another interface pointer. That is done using the IUnknown::QueryInterface method.
There
is no public data member’s exposure in COM objects. Public data is
exposured through methods we call accessors. In UMDF, we use a
Get/Retrieve or Set/Assign prefix for its read and write accessors,
respectively. Figure 1 shows the logical relationship between an object and its contents.
All
access to COM objects is through a virtual function table—commonly
called a VTable—that defines the physical memory structure of the
interface. The VTable is an array of pointers to the implementation of
each of the methods that the interface exposes. When a client gets a
pointer to an interface, it is actually a pointer to the VTable pointer,
which in turn points to the method pointer. For example, Figure 2 shows the memory structure of the VTable for IWDFloRequest.
The VTable is exactly the memory structure that many
C++ compilers create for a pure abstract base class. This is one of the
main reasons that COM objects are normally implemented in C++, with
interfaces declared as pure abstract base classes. You can then use C++
inheritance to implement the interface in your objects, and the VTable
is created for you by the compiler.
2. HRESULT
Before we look at using COM objects, let’s look at
the return from a COM method. COM methods often return a 32-bit type
called an HRESULT. It’s similar to the NTSTATUS type that Kernel Mode Driver routines use as a return value and is used in much the same way. Figure 3 shows the layout of an HRESULT.
The HRESULT type has three fields:
Severity, which is essentially a Boolean value that indicates success or failure
Facility, which can usually be ignored
Return code, which provides a more detailed description of the results
As with NTSTATUS values, it’s rarely necessary to parse the HRESULT and examine the individual fields. Standard HRESULT
values are defined in header files and described on method reference
pages. By convention, success codes are assigned names that begin with S_ and failure codes with E_. For example, S_OK is the standard HRESULT value for simple success.
It’s important not to think of HRESULT as error values. Methods often have multiple return values for success and for failure. S_OK is the usual return value for success, but methods sometimes return other success codes, such as S_FALSE.
The severity value is all that is needed to determine whether the method simply succeeded or failed. Rather than parse the HRESULT to get the Severity value, COM provides two macros that work much like the NT_SUCCESS macro that is used to check NTSTATUS values for success or failure. For an HRESULT return value of hr:
FAILED(hr)
Returns TRUE if the Severity code for hr indicates
failure and FALSE if it indicates success.
SUCCEEDED(hr)
Returns FALSE if the Severity code for hr indicates
failure and TRUE if it indicates success.
You can examine the HRESULT’s return code to determine whether a failure is actionable. Usually, you just compare the returned HRESULT
to the list of possible return values on the method’s reference page.
However, be aware that those lists are often incomplete. They typically
have only those HRESULTs that are specific to the method or standard HRESULTs that have some method-specific meaning. The method might also return other HRESULTs.
Always test for simple success or failure with the SUCCEEDED or FAILED macros, whether or not you test for specific HRESULT values. Otherwise, for example, if you test for success by comparing the HRESULT to S_OK and the method unexpectedly returns S_FALSE, your code will probably fail.
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, use the HRESULT_FROM_NT macro to convert the NTSTATUS value into an equivalent HRESULT. However, do not use this macro for an NTSTATUS value of STATUS_SUCCESS. Instead, return the S_OK HRESULT value. If you need to return a Windows error value, you can convert it to an HRESULT with the HRESULT_FROM_WIN32 macro.