In this article, you will find a way for making a lean and highly reusable COM component. You will also see the minimum steps required for registering a COM component.
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:
HRESULT _stdcall AddNumbers( long First, long Secong, long* Result );
HRESULT _stdcall SubtractNumbers( long First, long Second, long* Result );
HRESULT _stdcall MultiplyNumbers( long First, long Second, long* Result );
HRESULT _stdcall QueryInterface( REFIID riid, void** ppObj );
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
{
long m_nRefCount;
public:
virtual ULONG _stdcall AddRef()
virtual ULONG _stdcall Release();
...
};
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 )
QI( riid, IMyInterface, ppObj )
QI( riid, IAnotherInterface, ppObj )
QI( riid, IYetAnotherInterface, ppObj )
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:
HRESULT _stdcall QueryInterface( REFIID riid, void** ppObj )
{
if( riid == IID_IClassFactory )
{
*ppObj = static_cast< IClassFactory* >(this);
AddRef();
return S_OK;
}
else if( riid == IID_IUnknown )
{
*ppObj = static_cast< IUnknown* >(this);
AddRef();
return S_OK;
}
else
{
*ppObj = NULL;
return E_NOINTERFACE;
}
}
...
};
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:
virtual HRESULT _stdcall CreateInstance( IUnknown* pUnknown, REFIID riid, void** ppObj );
virtual HRESULT _stdcall LockServer( BOOL bLock );
...
};
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:
static HRESULT RegisterServer( TCHAR tcProgID[], TCHAR tcCLSID[], TCHAR tcThreadingModel[],
TCHAR tcPath[] );
static HRESULT UnregisterServer( TCHAR tcProgID[], TCHAR tcCLSID[] );
static HRESULT RegisterInterface( TCHAR tcInterfaceName[], TCHAR tcInterfaceID[],
TCHAR tcNumMethods[] );
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.
STDAPI DllRegisterServer()
{
TCHAR tcProgID[] = _T("Adeel");
TCHAR tcCLSID[] = _T("{D27733A2-F516-11d4-B219-0080C84499A8}");
TCHAR tcThreadingModel[] = _T("Apartment");
TCHAR tcPath[MAX_PATH];
GetModuleFileName( ( HMODULE )g_hInstance, tcPath, sizeof(tcPath) / sizeof(TCHAR) );
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 );
}
STDAPI DllUnregisterServer()
{
TCHAR tcProgID[] = _T("Adeel");
TCHAR tcCLSID[] = _T("{D27733A2-F516-11d4-B219-0080C84499A8}");
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
and DllUnregisterServer
. DllGetClassObject
would create an instance of the CComPtrFactory< YourClass >
and call QueryInterface
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.