|
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.
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: //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 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: // 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:
CRegistryManager
does all the above tasks. It uses registry API's 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 ); }
BOOL APIENTRY DllMain( HANDLE hDllHandle, DWORD dwReason, LPVOID lpReserved )
STDAPI DllGetClassObject( REFCLSID rClsid, REFIID riid, void** ppObj )
STDAPI DllCanUnloadNow()
STDAPI DllRegisterServer()
STDAPI DllUnregisterServer()
DllRegisterServer
and
DllUnregisterServer
.DllGetClassObject
would create an instance of the CComPtrFactory< YourClass >
and call
QueryInterface
upon that. QueryInterface
Congratulations you have made an ActiveX control from scratch that is based upon Multiple Inheritance
|
Home >>
COM / DCOM / COM+ >>
General
Advertise on The Code Project |
Article content copyright Adeel Abbas, 2001 everything else © CodeProject, 1999-2001. |