
| Last Modification: December 12, 2001 |
HOWTO: Reuse interface implementation in source in ATL projects - part I:
standalone interfaces
Reusing an interface implementation in binary form is quite hard in COM. You have to use either
containment/delegation or aggregation (if the object supports it). When you have the source code,
it is much simpler to reuse the implementation in source form. However you can't simply derive an
ATL object class from another ATL object class. This article provides several useful tips on how to
seamlessly reuse interface implementations in ATL projects. Also read
part II on the more advanced
subject of implementing base and derived interfaces in reusable fashion.
The simplest approach is to implement the interface in a standalone implementation class:
class IInterfaceImpl : public IInterface
{
// Implementation goes here
};
Then you inherit from the interface implementation class instead of the raw interface:
class ATL_NO_VTABLE CAtlObj :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CAtlObj, &CLSID_AtlObj>,
public IInterfaceImpl
{
public:
CAtlObj() {}
DECLARE_REGISTRY_RESOURCEID(IDR_ATLOBJ)
DECLARE_NOT_AGGREGATABLE(CAtlObj)
BEGIN_COM_MAP(CAtlObj)
COM_INTERFACE_ENTRY(IInterface)
END_COM_MAP()
};
No additional code and you can use this interface in as many COM object classes as you need to.
In many cases, however, the interface implementation will depend on other code in the object. Using C++ templates, the code can be customized to bind to services provided in other classes (base classes for the ATL object class). This is achieved by passing the ATL object class as a template parameter:
template <typename T>
class IInterfaceImpl : public IInterface
{
// Methods are implemented here
// In a method that requires services from the derived class
{
T* pT = static_cast<T*>(this);
pT->SomeMethod();
...
}
};
class ATL_NO_VTABLE CAtlObj :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CAtlObj, &CLSID_AtlObj>,
public IInterfaceImpl<CAtlObj>
{
public:
CAtlObj() {}
DECLARE_REGISTRY_RESOURCEID(IDR_ATLOBJ)
DECLARE_NOT_AGGREGATABLE(CAtlObj)
BEGIN_COM_MAP(CAtlObj)
COM_INTERFACE_ENTRY(IInterface)
END_COM_MAP()
};
SomeMethod is usually implemented in another base class of the ATL object class. A typical example are the Lock and Unlock methods implemented in CComObjectRootEx<>.
A known phenomenon resulting from unwise use of templates is called code bloat. The compiler will generate as many copies of the code for the template class as the number of unique combinations of the template parameters it is instantiated with. In the above example you'll get as many copies of the interface implementation as are the number of ATL object classes you use it in. This is clearly undesirable in most (if not all) cases.
To solve this problem, we can use a mixed approach. We isolate the implementation code that does not depend on any services from the ATL object class and put it into a standalone C++ class. This class does not need to derive from the interface (and it's more efficient if it doesn't so there are no vtable calls involved in the other class as we'll see shortly). We define the template as outlined above, but derive from our auxiliary class as well:
class CInterfaceImpl
{
// Helper methods for implementing IInterface
};
template <typename T>
class IInterfaceImpl :
public CInterfaceImpl,
public IInterface
{
// Implement IInterface methods by forwarding to CInterfaceImpl
// Example: locking required
{
T* pT = static_cast<T*>(this);
pT->Lock();
// Delegate to CInterfaceImpl
pT->Unlock();
}
};
Only the locking code is replicated for each ATL object class that derives from this template. It cannot be shared anyway.
Although only using the derived class as a template parameter was shown, you can put whatever template parameters are needed for customizing the interface implementation.
A note on dual interfaces: The above techniques are applicable to dial interfaces as well. The derivation in the ATL object class will change to:
class ATL_NO_VTABLE CAtlObj :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CAtlObj, &CLSID_AtlObj>,
public IDispatchImpl<IInterfaceImpl<CAtlObj>, &IID_IInterface, &LIBID_YourTypeLib>
{
public:
CAtlObj() {}
DECLARE_REGISTRY_RESOURCEID(IDR_ATLOBJ)
DECLARE_NOT_AGGREGATABLE(CAtlObj)
BEGIN_COM_MAP(CAtlObj)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IInterface)
END_COM_MAP()
};
References and samples: