Developing Drivers with the Windows Driver Foundation (Pro Developer)

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:

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 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

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. };

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:

 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

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); };

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

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. };

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

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

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

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

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

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

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

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; } }

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:

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

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); } }

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:

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

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 ); };

Some considerations for implementing UMDF callback objects include the following:

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.

Категории