A Multiple Inheritance based COM Framework






4.25/5 (4 votes)
Jul 17, 2001
5 min read

67202

1393
A simple COM server that is based on multiple inheritance
Introduction
I believe that every programmer who wishes to work under the covers of COM must write a COM component in plain C++. In this article, I will describe a way for making a lean and highly reusable COM component. I will also explain what are the minimum steps required for registering a COM component.
The book "Professional COM Applications with ATL by Sing Li and Panos Economopoulos" describes a way to make a COM component from scratch. The authors have used the nested class approach instead of multiple inheritance approach. This technique has several shortcomings because as more interfaces are added, the code becomes complex. Multiple inheritance removes this problem to some extent because the interfaces are more independent. Secondly since C++ classes expose multiple interfaces through multiple inheritance, it is more intuitive for a C++ programmer to think of a COM server as being multiply inherited from several interfaces.
Class Design
CMyComClass
is the implementation class that inherits from three interfaces, IMyInterface
, IAnotherInterface
and IYetAnotherInterface
. All of these interfaces are derived from IUnknown
. Each interface implements a method, so the declaration of CMyComClass
would be:
class CMyComClass : public IMyInterface , public IAnotherInterface, public IYetAnotherInterface
{
public:
//Declared in IMyInterface
HRESULT _stdcall AddNumbers( long First, long Secong, long* Result );
//Declared in IAnotherInterface
HRESULT _stdcall SubtractNumbers( long First, long Second, long* Result );
//Declared in IYetAnotherInterface
HRESULT _stdcall MultiplyNumbers( long First, long Second, long* Result );
//Must implement QueryInterface
HRESULT _stdcall QueryInterface( REFIID riid, void** ppObj );
//Must also include this macro in class declaration
DECLARE_IUNKNOWN_METHODS
};
As indicated in the class diagram, the conceptual design is based on the decorator design pattern, which means that your implementation class is sandwiched between CComPtr
class and the interfaces. CComPtr
implements reference counting through AddRef
and Release
. It always derives from your implementation class through the class bridging mechanism:
template< class T >
class CComPtr : public T
{
// Reference count
long m_nRefCount;
public:
// Increments reference count
virtual ULONG _stdcall AddRef()
// Decrements reference count and deletes if necessary
virtual ULONG _stdcall Release();
... // Constructor and Destructor omitted
};
Therefore, when I write CComPtr< CMyComClass >
, CComPtr
actually inherits from CMyComClass
and can perform tasks transparent to CMyComClass
. The only function that is left unimplemented is QueryInterface
. You must provide QueryInterface
implementation in CMyComClass
. I have included some macros that help in the implementation of QueryInterface
:
HRESULT _stdcall CMyComClass::QueryInterface( REFIID riid, void** ppObj )
{
QIIUNKNOWN( riid, ppObj ) // Check for IUnknown
QI( riid, IMyInterface, ppObj ) // Check for IMyInterface
QI( riid, IAnotherInterface, ppObj ) // Check for IAnotherInterface
QI( riid, IYetAnotherInterface, ppObj ) // Check for IYetAnotherInterface
return E_NOINTERFACE;
}
If riid
is equal to IID_IUnknown
, QIIUNKNOWN
casts *ppObj
to this and returns S_OK
. QI
performs a similar operation but with other interfaces. For QueryInterface
to work properly, you must write QIIUNKNOWN
once and QI
for all the interfaces that your class inherits from. Since MyComClass
inherits from these three interfaces, so I have written QI
for each one of them.
CComPtrFactory
is the class factory that will be used to create instances of our COM server. CComPtrFactory
should use the reference counting mechanism of CComPtr
for its memory management also. One way of achieving this is by inheriting CComPtrFactory
from CComPtr
(just as IClassFactory
inherits from IUnknown
). But the CComPtr
can only inherit from the class that implements QueryInterface
. That's why we need a class CComPtrFactoryQIImpl
that lies in between CComPtr
and IClassFactory
and implements QueryInterface
for class factory.
class CComPtrFactoryQIImpl : public IClassFactory
{
public:
// QueryInterface implementation for the class factory class
HRESULT _stdcall QueryInterface( REFIID riid, void** ppObj )
{
if( riid == IID_IClassFactory )
{ // Check for IID_IClassFactory
*ppObj = static_cast< IClassFactory* >(this);
AddRef();
return S_OK;
}
else if( riid == IID_IUnknown )
{ // Check for IID_IUnknown
*ppObj = static_cast< IUnknown* >(this);
AddRef();
return S_OK;
}
else
{ // no interface created
*ppObj = NULL;
return E_NOINTERFACE;
}
}
... // constructor and destructor omitted
};
CComPtrFactory
then inherits from CComPtr
(which inherits from CComPtrFactoryQIImpl
) and as a result, it doesn't have to implement AddRef
, QueryInterface
and Release
. Now all that is left is to implement CreateInstance
and LockServer
and our class factory is ready to create COM objects. The implementation of these functions is pretty straight forward which I am sure you would be able to understand.
template< class T >
class CComPtrFactory : public CComPtr< CComPtrFactoryQIImpl >
{
public:
// Creates CComPtr object and retrieves an interface pointer to this object.
virtual HRESULT _stdcall CreateInstance( IUnknown* pUnknown, REFIID riid, void** ppObj );
// Calling LockServer allows a client to hold onto a class factory so that
// multiple objects can be created quickly.
virtual HRESULT _stdcall LockServer( BOOL bLock );
... // constructor and destructor omitted
};
Now I would describe the minimum steps required for registering a COM server. In order to correctly register a COM server, the following entries must be installed in the system registry:
- HKEY_CLASSES_ROOT\(progid of component)
- HKEY_CLASSES_ROOT\(progid of component)\Default: (progid of component)
- HKEY_CLASSES_ROOT\(progid of component)\CLSID
- HKEY_CLASSES_ROOT\(progid of component)\CLSID\Default: (CLSID of component)
With these entries, you can only access the default interface. So in order to access the other interfaces, you must also install the information about the interfaces also. This can be achieved by defining the following entries in the registry for each interface:
- HKEY_CLASSES_ROOT\Interface\(Interface id)
- HKEY_CLASSES_ROOT\Interface\(Interface id)\Default: (Interface name)
- HKEY_CLASSES_ROOT\Interface\(Interface id)\NumMethods
- HKEY_CLASSES_ROOT\Interface\(Interface id)\NumMethods\Default: (Number of methods exposed by the interface)
The class CRegistryManager
does all the above tasks. It uses registry APIs to register and unregister interface of server. Its declaration is:
class CRegisteryManager
{
public:
// Enters progid, clsid, path and threading model entries in the registry
static HRESULT RegisterServer( TCHAR tcProgID[], TCHAR tcCLSID[], TCHAR tcThreadingModel[],
TCHAR tcPath[] );
// Removes server entries from the registry
static HRESULT UnregisterServer( TCHAR tcProgID[], TCHAR tcCLSID[] );
// Registers interface name and id in the registry
static HRESULT RegisterInterface( TCHAR tcInterfaceName[], TCHAR tcInterfaceID[],
TCHAR tcNumMethods[] );
// Removes interface entries from the registry
static HRESULT UnregisterInterface( TCHAR tcInterfaceID[] );
};
You have to call these functions in DllRegisterServer
and DllUnregisterServer
. RegisterInterface
and UnregisterInterface
should be called for all the interfaces in the type library. Copy the interface name and id from the idl file. Also specify the number of methods exposed by the interface. Remember this also includes methods that the interface inherits. Since all of these inherit from IUnknown
and implement one method, number of methods should be 4 for all of them.
// Instructs an in-process server to create its registry entries for all classes supported
STDAPI DllRegisterServer()
{
TCHAR tcProgID[] = _T("Adeel");
TCHAR tcCLSID[] = _T("{D27733A2-F516-11d4-B219-0080C84499A8}");
TCHAR tcThreadingModel[] = _T("Apartment");
TCHAR tcPath[MAX_PATH];
// Get path of the dll
GetModuleFileName( ( HMODULE )g_hInstance, tcPath, sizeof(tcPath) / sizeof(TCHAR) );
// Call for all the interfaces in the type library
CRegisteryManager::RegisterInterface( _T("IMyInterface"),
_T("{D27733A0-F516-11d4-B219-0080C84499A8}"), _T("4") );
CRegisteryManager::RegisterInterface( _T("IAnotherInterface"),
_T("{5159BAC6-C5F2-457e-8FA7-5725A8B2A6AD}"), _T("4") );
CRegisteryManager::RegisterInterface( _T("IYetAnotherInterface"),
_T("{C2C02F3A-53D9-4cc0-991B-33A37AAB1DC3}"), _T("4") );
return CRegisteryManager::RegisterServer( tcProgID, tcCLSID, tcThreadingModel, tcPath );
}
// Instructs an in-process server to remove
// only those entries created through DllRegisterServer.
STDAPI DllUnregisterServer()
{
TCHAR tcProgID[] = _T("Adeel");
TCHAR tcCLSID[] = _T("{D27733A2-F516-11d4-B219-0080C84499A8}");
// Call for all the interfaces in the type library
CRegisteryManager::UnregisterInterface( _T("{D27733A0-F516-11d4-B219-0080C84499A8}") );
CRegisteryManager::UnregisterInterface( _T("{5159BAC6-C5F2-457e-8FA7-5725A8B2A6AD}") );
CRegisteryManager::UnregisterInterface( _T("{C2C02F3A-53D9-4cc0-991B-33A37AAB1DC3}") );
return CRegisteryManager::UnregisterServer( tcProgID, tcCLSID );
}
Problems
The one obvious problem that MI based design always has is the name conflict. So if you want to have two interfaces that have a method with the same name but different implementation, this article doesn't help your cause. Another problem is that virtual inheritance is not allowed in COM interfaces, so there would be multiple copies of IUnknown
. But since IUnknown
only has three pure virtual
methods, this would become a serious issue only if your component exposes thousands of interfaces.
How to Use It
- Create a new
Win32DLL
project. - Export the following functions by defining them and writing them in a .def file (included in source code):
BOOL APIENTRY DllMain( HANDLE hDllHandle, DWORD dwReason, LPVOID lpReserved ) STDAPI DllGetClassObject( REFCLSID rClsid, REFIID riid, void** ppObj ) STDAPI DllCanUnloadNow() STDAPI DllRegisterServer() STDAPI DllUnregisterServer()
- Implement Registering and Unregistering functionality inside
DllRegisterServer
andDllUnregisterServer
. DllGetClassObject
would create an instance of theCComPtrFactory< YourClass >
and callQueryInterface
upon that.- Create the idl file that defines all the interfaces and include that file in the project.
- Include FrameworkClasses.cpp in the project.
- Inherit your class from the interface(s) and provide implementation for all the methods and also for
QueryInterface
.
Congratulations! You have made an ActiveX control from scratch that is based upon Multiple Inheritance.
History
- 17th July, 2001: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.