How to Implement UMDF Callback Objects


A UMDF driver consists predominantly of a group of COM callback objects that respond to notifications by the UMDF runtime and allow the driver to handle events such as read or write requests. All callback objects are in-process COM objects. The basic requirements for implementing callback objects are relatively simple:

  • Implement the IUnknown methods to handle reference counting and provide pointers to the object's interfaces.

  • Implement the UMDF callback interfaces that the object exposes.

This section discusses the basics of how to implement callback objects for UMDF drivers.

How to Implement a Class for a COM Object

Interfaces are not implemented in isolation; they must be exposed by an object. A typical approach for a relatively simple COM object is to implement the object as a C++ class that contains the code to support IUnknown and the UMDF interfaces that the object exposes. Implementing a COM object is thus largely about implementing the methods that make up its interfaces. Some basic considerations:

  • The class must inherit from every interface that it exposes. However, it can do so indirectly by inheriting from a parent class that in turn inherits from one or more of the required interfaces. For example, many of the callback objects in the UMDF samples do not inherit directly from IUnknown. Instead, they inherit from a class named CUnknown, which inherits from IUnknown.

  • Interfaces are declared as abstract base classes, so the class must implement all the interface methods.

  • The class can inherit from a parent class in addition to interfaces.

  • The class can also 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 should be used only to initialize class members and to do any other initialization that cannot fail.

    Put any code that can fail in a public initialization method that can be called after object creation. For an example of such a method, see CMyDevice::Initialize in the Fx2_Driver sample's Device.cpp file.

The interface methods are implemented as public methods of the class. The example in Listing 18-12 is a schematic declaration for a class named CMyObject that implements IUnknown plus two additional interfaces: IWDF1 and IWDF2.

Listing 18-12: A typical class declaration for a simple COM object

image from book
 class CMyObject : public IUnknown, public IWDF1, public IWDF2 { private: //Private utility methods and data public:      // IUnknown methods.      virtual ULONG STDMETHODCALLTYPE AddRef(VOID);      virtual ULONG STDMETHODCALLTYPE Release(VOID);      virtual HRESULT STDMETHODCALLTYPE QueryInterface(          __in REFIID InterfaceId,          __out PVOID *Object);      //IWDF1 methods      . . .// Code omitted for brevity.      //IWDF2 methods      . . .// Code omitted for brevity. }; 
image from book

How to Implement IUnknown

IUnknown is the core COM interface that every COM object exposes and is essential to the object's operation. IUnknown can be implemented in several ways. The approach in this chapter is the one that the UMDF samples use:

  • A class called CUnknown implements a base version of IUnknown.

  • The bulk of the object is implemented in a separate class that inherits from CUnknown and includes interface-specific implementations of the IUnknown methods.

 Tip  See Inside COM, Inside Ole, and the COM documentation on MSDN for a discussion of other approaches to implementing IUnknown.

The example in Listing 18-13 is an edited version of the CUnknown declaration from the Fx2_Driver sample. It shows the parts of the class that are essential to.

Listing 18-13: A class declaration for the Fx2_Driver sample's base implementation of IUnknown

image from book
 class CUnknown : public IUnknown { private:     LONG m_ReferenceCount; //The reference count public:     virtual ULONG STDMETHODCALLTYPE AddRef(VOID);     virtual ULONG STDMETHODCALLTYPE Release(VOID);     virtual HRESULT STDMETHODCALLTYPE QueryInterface(                                           __in REFIID InterfaceId,                                           __out PVOID *Object); }; 
image from book

The example in Listing 18-14 is an edited version of the class declaration for the Fx2_Driver sample's driver callback object, showing the IUnknown methods.

Listing 18-14: A class declaration for the Fx2_Driver sample's driver callback object

image from book
 class CMyDriver : public CUnknown, public IDriverEntry { private:     . . .// Code omitted for brevity. public:     //IDriverEntry-specific implementation of IUnknown     virtual ULONG STDMETHODCALLTYPE AddRef(VOID);     virtual ULONG STDMETHODCALLTYPE Release(VOID);     virtual HRESULT STDMETHODCALLTYPE QueryInterface(         __in REFIID InterfaceId,         __out PVOID *Object         );     //IDriverEntry implementation     . . .// Code omitted for brevity. }; 
image from book

AddRef and Release

Reference counting is arguably the key task of IUnknown. Usually, a single reference count is maintained for the object as a whole, even though AddRef and Release can be called on any interface. The samples handle this requirement by having the interface-specific implementations pass their calls to the base implementation and letting those methods handle incrementing or decrementing the data member that holds the reference count. That way, all AddRef and Release requests are handled in one place, reducing the chances of making an error.

The code in Listing 18-15 is a typical example of an AddRef implementation, taken from the Fx2_Driver sample's IUnknown base implementation. m_ReferenceCount is the private data member that holds the reference count. Notice the use of InterlockedIncrement rather than the ++ operator. InterlockedIncrement locks the reference count while it is being incremented, eliminating the possibility of a race condition causing problems.

Listing 18-15: Fx2_Driver sample's base implementation of AddRef

image from book
 ULONG STDMETHODCALLTYPE CUnknown::AddRef(VOID) {     return InterlockedIncrement(&m_ReferenceCount); } 
image from book

Release is similar to AddRef but slightly more complicated. Release decrements the reference count and then checks whether it is zero. If so, there are no active interfaces and Release uses the C++ delete operator to destroy the object. The example in Listing 18-16 is from the Fx2_Driver sample's base implementation of Release.

Listing 18-16: Fx2_Driver sample's base implementation of Release

image from book
 ULONG STDMETHODCALLTYPE CUnknown::Release(VOID) {     ULONG count = InterlockedDecrement(&m_ReferenceCount);     if (count == 0) {         delete this;     }     return count; } 
image from book

Both AddRef and Release return the current reference count, which is sometimes useful for debugging.

The interface-specific implementations of AddRef and Release simply use the __super keyword to call the base implementation of the method and return that method's return value. The example in Listing 18-17 shows an interface-specific implementation of AddRef. The Release implementation is similar.

Listing 18-17: An interface-specific implementation of AddRef

image from book
 ULONG STDMETHODCALLTYPE CMyDriver::AddRef(VOID) {     return __super::AddRef(); } 
image from book

QueryInterface

QueryInterface is the fundamental mechanism by which a COM object provides pointers to its interfaces. It responds to client requests by returning the specified interface pointer. The example in Listing 18-18 is a typical QueryInterface implementation and is a slightly modified version of the Fx2_Driver sample's base implementation of IUnknown.

Listing 18-18: Fx2_Driver sample's base implementation of IUnknown

image from book
 HRESULT STDMETHODCALLTYPE CUnknown::QueryInterface(     __in REFIID InterfaceId,     __out PVOID *Interface     ) {     if (IsEqualIID(InterfaceId, __uuidof(IUnknown))) {         AddRef();         *Interface = static_cast<IUnknown *>(this);         return S_OK;     }     else {         *Interface = NULL;         return E_NOINTERFACE;     } } 
image from book

QueryInterface first checks the requested IID to see if it specifies a supported interface. In this example, the only supported interface is IUnknown, so there is only one valid IID. The method uses two convenient utilities to test the IID:

  • IsEqualIID, which is a utility function declared in Guiddef.h that returns TRUE if the IIDs are equal and FALSE otherwise.

  • __uuidof, which is a Microsoft-specific C++ operator that returns the IID of a specified interface type.

If the requested interface is supported, QueryInterface calls AddRef to increment the object's reference count and returns an interface pointer. To return the pointer, QueryInterface casts a this pointer to the requested interface type. This cast is required because of the way in which C++ handles multiple inheritance. Casting this to the appropriate interface type ensures that the pointer is at the right position in the VTable.

If the requested IID is not supported, QueryInterface sets the Interface value to NULL and returns a standard HRESULT error value, E_NOINTERFACE.

Important 

All of an object's QueryInterface implementations must return the same IUnknown pointer. For example, if an object exposes two interfaces, InterfaceA and InterfaceB, a client that calls QueryInterface on either interface must receive the same IUnknown pointer.

The UMDF samples' interface-specific implementations of IUnknown return an interface pointer only if the request is for that particular interface. They forward all other requests to the base implementation. The example in Listing 18-19 is from the Fx2_Driver sample's implementation of QueryInterface for IDriverEntry. The example returns an IDriverEntry pointer when that interface is requested and passes all other requests to the base implementation.

Listing 18-19: Fx2_Driver sample's base implementation of QueryInterface

image from book
 HRESULT CMyDriver::QueryInterface(     __in REFIID InterfaceId,     __out PVOID *Interface     ) {     if (IsEqualIID(InterfaceId, __uuidof(IDriverEntry))) {         AddRef();         *Interface = static_cast<IDriverEntry*>(this);         return S_OK;     }     else {         return CUnknown::QueryInterface(InterfaceId, Interface);     } } 
image from book

Each callback object usually has a one-to-one relationship with a corresponding UMDF object. For example, a driver callback object is associated with the UMDF driver object. The callback object serves two primary purposes:

  • Receiving notifications from the UMDF runtime that are related to the associated UMDF object.

  • Providing a context area to store context data for the associated UMDF object.

UMDF callback objects expose IUnknown plus at least one UMDF interface such as IDriverEntry or IPnPCallback. The number and types of UMDF interfaces that a callback object exposes depend on the particular object and the requirements of the driver. Objects typically have at least one required interface. For example, the driver callback object must expose one UMDF interface: IDriverEntry.

Many callback objects also have one or more optional interfaces that are implemented only if the driver requires them. For example, the device callback object can expose several optional UMDF interfaces-including IPnpCallback, IPnpCallbackHardware, and IPnpCallbackSelfManagedIo-depending on driver requirements.

Most of the details of implementing the UMDF interfaces are related to individual methods and are not discussed here.

A UMDF callback object is typically implemented as a class that inherits from IUnknown and one or more object-specific interfaces. The example in Listing 18-20 shows the full declaration of the CMyDriver class, from the Fx2_Driver sample's driver callback object. The class inherits from a single UMDF interface-IDriverEntry-and inherits from IUnknown through the CUnknown parent class. For convenience, a number of the simpler methods are implemented here, rather than in the associated .cpp file.

Listing 18-20: Declaration of the Fx2_Driver sample's driver callback object

image from book
 class CMyDriver : public CUnknown, public IDriverEntry { private:     IDriverEntry * QueryIDriverEntry(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)     {         UNREFERENCED_PARAMETER(FxWdfDriver);         return S_OK;     }     virtual HRESULT STDMETHODCALLTYPE OnDeviceAdd(         __in IWDFDriver *FxWdfDriver,         __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 REFIID InterfaceId,         __deref_out PVOID *Object         ); }; 
image from book

Some considerations for implementing UMDF callback objects include the following:

  • Classes must inherit from every interface that they expose.

    However, classes can do so indirectly, for example, by inheriting from a class that in turn inherits from one or more interfaces.

  • Classes must implement all the methods on the interfaces that they expose.

    This is required because interfaces are declared as abstract base classes.

  • Classes can contain private data members, public methods that are not part of an interface, and so on.

    These are for internal use by the object or by other objects in the DLL and are not visible to clients.

  • Classes can have accessor methods to publicly expose data members.

It is a good practice to have constructors to initialize members of the class and perform any other initialization that is certain not to fail. Constructors should contain no code that might fail. Put any code that might fail in a public initialization method that can be called after object creation. For an example of such a function, see the CMyDevice::Initialize method in the Fx2_Driver sample's Device.cpp file.




Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net