ImplementQI.H

ImplementQI.H defines a template class that provides a default implementation of QueryInterface. You use it by deriving off of it. e.g.

#include "ImplementQI.h"

class CDog : public ImplementQI<IDog, IAnimal>
{
	// IDog methods
	// IAnimal methods
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();
};

It also comes with some classes which you can use to implement AddRef and Release in some common ways:

class CDog : public MultiThreaded::HeapCounter<ImplementQI<IDog, IAnimal> >
{
	// Object created with 0 reference count, AddRef()/Release()
	// are performed with InterlockedXXXX functions.
	// Last Release performs "delete this" (virtual destructor is used).
};

class CDog : public SingleThreaded::HeapCounter<ImplementQI<IDog, IAnimal> >
{
	// Same as before - AddRef and Release are not protected for multithreaded access.
};

class CDog : public StaticCounter<ImplementQI<IDog, IAnimal> >
{
	// AddRef() returns 2
	// Release() returns 1
};

Unlike ATL, CDog remains as the most derived class in the hierarchy, which means that you can use constructor arguments!

History

Motivation

ATL is really great at generating small, lightweight COM servers, but there's some things I really don't like about it:

These templates provide an alternative way of implementing COM interfaces without having to use ATL. Because you can use constructor arguments, there are some situations (particular implementing callback interfaces in client code), in which I prefer them to ATL.

Beware server lifetime

If a reference to an object instance is created by ATL, it automatically manages a server lock count (by calling _Module.Lock and _Module.Unlock()). When you are using ATL, creating a new object and "AddRef"ing it will ensure that your DLL or EXE server will stay loaded.

So it's important to remember then that you a manage your implementation of DllCanUnloadNow appropriately for any COM objects that you implement using ImplementQI.

One way to integrate ImplementQI COM objects with ATL would be to write an AtlHeapObject in similar vain to the current HeapObject classes. It would call _Module.Lock() in its constructor and _Module.Unlock() in its destructor.

How does it work?

The template relies on there being an association between a specific interface type and an Interface ID (IID). This association is defined by whatever is returned by:

InterfaceIID<theinterface>::iid()

InterfaceIID is a template class whose default implementation makes use of the VC++ specific __uuidof operator. The appropriate declspec(uuid()) expressions are generated automatically by #import and MIDL. If you don't want to use __uuidof, you can supply your own implementation by supplying a specialisation of this class. A macro has been provided to do this:

DECLARE_IID(Type, refiid);

The above macro declares a specialisation of the InterfaceIID class for your interface. You don't have to use the macro if you don't want to :-)

Example: Associating IID_IDog with the type IDog:
Using the DECLARE_IID macro:
DECLARE_IID(IDog, IID_IDog)
Declaring a template specialisation
struct InterfaceIID<IDog> {
	static const IID &iid() { return IID_IDog; }
};

The ImplementQI implementation of QueryInterface tries to match the request IID against the associated IID of each of the template arguments that you specified. If it finds a match, it returns a static cast of that interface back to you. One benefit of this inheritance based approach is that ambiguous casts are automatically handled - it returns the first interface that matches. Hence, a QI for IUnknown always returns your object casted to the first template parameter.

Interface Inheritance

What if, for example, you have the following interface defined:

[ attributes ]
interface IAnimal : IUnknown
{
	HRESULT Move([in] int xPos, [in] int yPos);
};

[ attributes ]
interface ICat : IAnimal {
	HRESULT Meow();
};

You'll want any implementation of ICat to also respond to a QueryInterface for IID_IAnimal by supplying the ICat interface. To make ImplementQI behave in this way, define ICat as a base of IAnimal by specializing the InterfaceBase type for ICat:

struct InterfaceBase<ICat>
{
	typedef IAnimal Type;
};

The above specialisation indicates that any QueryInterface request for IID_IAnimal can be satisfied by supplying an ICat implementation. You can also use the DECLARE_BASE macro which is less verbose:

DECLARE_BASE(Child, DerivedFrom)
e.g.
DECLARE_BASE(ICat, IAnimal)

You then implement ICat and IAnimal by writing the following:

class CCat : public ImplementQI<ICat>
{
	// ICat methods
	STDMETHOD(Move)(int, int);
	// IAnimal methods
	STDMETHOD(Meow)();

	// AddRef/Release
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();
};

How efficient is it?

Terrific news! The old version used to impose a memory overhead per object instance. For example, if you wrote a class that implemented 1 COM interface using ImplementQI, it used to have a size of 16 bytes - 4 bytes for the mandatory vptr, plus 12 additional "junk" bytes. So 10000 instances would be wasting 160Kb of unnecessary memory. The new version completely removes this overhead. A class that implements 1 interface will have a size of 4 bytes, because of the vptr. A class that implements 2 interfaces will have a size of 8 bytes, and so on.

The remaining issue is one of code-bloat. I looked at the generated assembly code: each separate instantiation increases the size of your .DLL or .EXE by 87 bytes (to implement 1 interface) up to somewhere around 450 bytes (to implement 8 interfaces). It got a bit hard to judge because the 8 interface version no longer inlined everything (to my annoyance) and generates larger than necessary code that calls functions. This is far from severe and probably won't have a significant impact for most projects. However, it is unnecessary overhead. This code-bloat can be significantly reduced by having the templates instead generate a static table of the required information, in the same way ATL works. Watch this space :-) - I want to do it purely for the satisfaction of being able to state that this template is at least as efficient as ATL ;-)

This page last modified on