Click here to Skip to main content
15,884,836 members
Articles / Desktop Programming / ATL

A Multiple Inheritance based COM Framework

Rate me:
Please Sign up or sign in to vote.
4.25/5 (4 votes)
31 Jul 20015 min read 66.8K   1.4K   35   1
A simple COM server that is based on multiple inheritance
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

Image 1

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:

C#
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:

C#
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:

C#
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.

C#
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.

C#
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:

  1. HKEY_CLASSES_ROOT\(progid of component)
  2. HKEY_CLASSES_ROOT\(progid of component)\Default: (progid of component)
  3. HKEY_CLASSES_ROOT\(progid of component)\CLSID
  4. 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:

  1. HKEY_CLASSES_ROOT\Interface\(Interface id)
  2. HKEY_CLASSES_ROOT\Interface\(Interface id)\Default: (Interface name)
  3. HKEY_CLASSES_ROOT\Interface\(Interface id)\NumMethods
  4. 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:

C#
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.

C#
// 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

  1. Create a new Win32DLL project.
  2. Export the following functions by defining them and writing them in a .def file (included in source code):
    C#
    BOOL APIENTRY DllMain( HANDLE hDllHandle, DWORD dwReason, LPVOID lpReserved )
    STDAPI DllGetClassObject( REFCLSID rClsid, REFIID riid, void** ppObj )
    STDAPI DllCanUnloadNow()
    STDAPI DllRegisterServer()
    STDAPI DllUnregisterServer()
  3. Implement Registering and Unregistering functionality inside DllRegisterServer and DllUnregisterServer.
  4. DllGetClassObject would create an instance of the CComPtrFactory< YourClass > and call QueryInterface upon that.
  5. Create the idl file that defines all the interfaces and include that file in the project.
  6. Include FrameworkClasses.cpp in the project.
  7. 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.


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralInherting from CMyComClass Pin
nalbion1-Oct-02 6:53
nalbion1-Oct-02 6:53 
If I want to create a new class that inherits from CMyComClass, but has new functions, I need to create a new Interface class:

<br />
interface INalbionInterface : IMyInterface<br />
{ <br />
  HRESULT DivideNumbers([in] DWORD First, [in] DWORD Second, [out] DWORD *Result);<br />
}<br />
<br />
CNalbionComClass : public INalbionInterface, public CMyComClass<br />
{<br />
public:<br />
  HRESULT _stdcall DivideNumbers( long First, long Secong, long* Result );<br />
}<br />
/*******************************/<br />


The reason I'm trying to do this is so that from my application, I can do something like the following:

<br />
CMyComClass *pItem;<br />
<br />
switch( a )<br />
{<br />
  case 0:<br />
    pItem = new CNalbionComClass;<br />
    break;<br />
  case 1:<br />
    pItem = new CAdeelComClass;<br />
    break;<br />
  default:<br />
    break;<br />
}<br />
/*******************************/<br />


However, when I got to compile, I get an error message of the form:

<br />
Compiling...<br />
Spam.cpp<br />
O:\NalbionCom\Spam.cpp(369) : error C2259: 'CNalbionComClass' : cannot instantiate abstract class due to following members:<br />
        o:\NalbionCom\NalbionComClass.h(119) : see declaration of 'CNalbionComClass'<br />
O:\NalbionCom\Spam.cpp(369) : warning C4259: 'long __stdcall IMyInterface::AddNumbers(long, long, long *)' : pure virtual function was not defined<br />
        o:\NalbionCom\IMyInterface.h(532) : see declaration of 'AddNumbers'<br />


(or something along those lines)
...and before you say it, yes, I have implemented all virtual functions in the class CNalbionComClass

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.