Using COM to Develop UMDF Drivers, Part 2
- Using COM to Develop UMDF Drivers (Part 2 of 2)
- Implementing <tt>QueryInterface</tt>
Part 1 of this series examined the overall architecture of using COM to create user mode drivers. This article concludes this discussion by looking at the additional details involved in completing your user mode driver using COM.
Reference Counting
Unlike C++ objects, a COM client doesn't 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, it increments the reference count. When the client is finished with the 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 0 and the object destroys itself.
Basic Infrastructure Implementation
In this section, we'll look at the required basic infrastructure to support UMDF drivers.
Performing Tasks with the DllMain Function
A dynamic link library (DLL) can contain any number of in-process COM objects, but it must have a single entry point named DllMain. Windows calls DllMain after the binary has been loaded into a host process and before it's unloaded. The function is also called when threads are created or destroyed. The dwReason parameter indicates why the function was called.
When a UMDF driver's DllMain function is called for DLL loading or unloading, it should perform only simple module-wide initialization and termination tasks, such as initializing/freeing global variables, or registering/unregistering Windows software trace preprocessor (WPP) tracing. There a number of things DllMain definitely should not do, such as calling LoadLibrary. When a UMDF driver's DllMain function is called for thread creation or destruction, it can ignore the call.
Accessing Class Factories with DllGetClassObject
Because class factories aren't exported by name, there's no direct way for a client to get access to them. Instead, the DLL exports the DllGetClassObject function by name, which allows it to be called by any client with access to the DLL. For many COM DLLs, including the UMDF samples, DllGetClassObject is the only function listed in the project's .def file to be exported by name from the DLL.
When a client wants to create an instance of one of the COM objects in the DLL, it passes the CLSID of the desired class factory object to DllGetClassObject and the IID of the desired interface, usually IClassFactory. DllGetClassObject creates a new class factory object and returns a pointer to the appropriate interface on the object. The client can then use the IClassFactory::CreateInstance method to create an instance of the object.
Working with the Driver Object's Class Factory
Some COM objects must be created by external clients. For UMDF drivers, there is usually only one such object: the driver callback object. A COM object that can be created by an external client must have a class factory. This is a small specialized COM object and returns a pointer to a specified interface.
Class factories usually expose only one interface in addition to IUnknown: the IClassFactory interface, which has two members:
- CreateInstance creates an instance of the object and returns the requested interface pointer to the client.
- LockServer can be used to keep the DLL in memory. UMDF class factories typically have only a token implementation because UMDF doesn't use LockServer.
Some recommendations for implementing CreateInstance:
- Ignore the first parameter. Its purpose is to support COM aggregation, which isn't used by UMDF.
- Create a new driver callback object by whatever means is convenient. The sample code puts the object-creation code in a static method on the class that implements the callback object.
- Return the appropriate interface as an OUT parameter. At this point, the object should have a reference count of 1.
Implementing a UMDF Callback Object
A UMDF driver consists of a collection of COM callback objects. These objects respond to notification by the UMDF runtime and allow the driver to process various events, such as read or write requests. All callback objects are in-process COM objects. This means that they're packaged in a DLL and run in the process context of a UMDF host.
The basic requirements for implementing UMDF callback objects are relatively simple and straightforward:
- Implement the IUnknown methods to handle reference counting and provide pointers to the object's interface.
- Implement the methods of the UMDF callback interfaces that will be exported by the object.
Implementing the UMDF Callback Class
UMDF callback objects are typically implemented as a C++ class that contains the code to support IUnknown plus any UMDF interfaces that the object exposes. The UMDF interfaces are declared in wudfdd.h. Following are some of the requirements:
- The class must inherit from every interface that it exposes. However, it can do so indirectly; for example, by inheriting from a class that in turn inherits from one or more interfaces.
- Interfaces are declared as abstract base classes, so the class must implement all the interface methods.
- The class often inherits from a parent class in addition to interfaces. Many of the UMDF samples, for instance, inherit from a parent class named CUnknown, which contains a base implementation of IUnknown.
- The class can contain private data members, public methods that are not part of an interface, and so on. These are for internal use and are not visible to clients.
- Constructors are optional. However, if a class has a constructor, it should contain no code in it that fails. Put any code that can fail in a public initialization method that can be called after object creation.
As mentioned earlier, a UMDF callback object is typically implemented as a class that inherits from IUnknown and one or more object-specific interfaces. Listing 1 shows the full declaration of the CMyDriver class. This class inherits from a single UMDF interface IDriverEntry and inherits from IUnknown through the CUnknown parent class. For convenience, several of the simpler methods are implemented here, rather than in the associated .cpp file.
Listing 1 Declaration of a driver's callback object.
Class CMyDriver : public Unknown, Public IDriverEntry { private: IDriveEntry * QueryIdriveEntry (VOID) { AddRef(); return static_cast<IDriverEntry*>(this); } HRESULT initialize(VOID) public: static HRESULT CreateInstance)__out PCMYDriver *Driver); public: virtual HRESULT STDMETHODCALLTYPE OnInitialize(__in IWDFDriver *FxWdfDriver) { UNREFERENED_PARAMETER (FxWdfDriver); return S_OK; } virtual HRESULT STDMETHODCALLTYPE OnDeviceAdd( __in IWDFDriver *FwWdfDriver, __in IWDFDeviceInitialize *FxDeviceInit); virtual VOID STDMETHODCALLTYPE OnDeInitialize( __in IWDFDriver *FxWdfDriver) { UNREFERENCED PARAMETER (FxWdfDriver); return; } virtual ULONG_STDMETHODCALLTYPE AddRef (VOID) { return __super::AddRef(); } virtual ULONG STDMETHODCALLTYPE Release(VOID) { return __super::Release(); } virtual HRESULT STDMETHODCALLTYPE QueryInterface( __in REFID InterfaceID, __deref_out PVOID *Object); };
IUnknown is the core COM interface; it's exposed in every COM object and is essential to the object's operation. The approach used by the UMDF sample code is to have an IUnknown base class, called CUnknown, plus an implementation for each exposed interface that inherits from the base class.
Implementing AddRef and Release
Reference counting is arguably the key task of IUnknown. Normally, a single reference count is maintained for the object as a whole. Following are some recommendations for handling AddRef and Release:
- Interface-specific implementations should pass their calls to the base implementation and let it handle incrementing or decrementing the reference count for the object.
- Use InterlockedIncrement and interlockedDecrement to modify the reference count. This eliminates the possibility of a race condition.
- After the Release method decrements the reference count, check to see whether the count has gone to 0. If so, there are no outstanding interface pointers and you can delete to destroy the object.
- Both AddRef and Release return the current reference count. Use this technique for debugging purposes.