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 be explaining 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 beacuse 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, 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 out 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 API's 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 ) <br>
STDAPI DllGetClassObject( REFCLSID rClsid, REFIID riid, void** ppObj ) <br>
STDAPI DllCanUnloadNow() <br>
STDAPI DllRegisterServer() <br>
STDAPI DllUnregisterServer()<br>
- 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