Library Zone Articles
External Articles
Byte Size

Discovery Zone Catalogue
Diary
Links
Bookstore
Interactive Zone Ask the Gurus
Discussion Groups
Newsletters
Feedback
Etc Cartoons
Humour
COMpetition
Advertising
Site Builder ASP Web Ring ASP Web Ring

Click here
The Developer's Resource & Community Site
COM XML ASP Java & Misc. NEW: VS.NET
International This Week Forums Author Central Find a Job

Active Template Library: Architecture and Internals - part 2

Download print article

For the previous article in this series, please go to https://www.idevresource.com/com/library/articles/atlinternals.asp.

In the last article, we talked about the internals of ATL, avoiding somewhat intricate and involved discussions. The aim then was to understand the architecture as a whole.

This article will cover the details of the internals of ATL. ATL's design is carefully thought of to reduce code size and increase performance (sometimes one at the loss of other, but how many frameworks have you come across that even give you the option of choosing one?). Certain topics in this article might have been covered briefly in the previous article, but most of them will be new (that is if you are new to ATL internals).

These topics are:

  • Thread model
  • Dual interface support
  • Object activation (Standalone and Aggregation)
  • Error handling while object creation
  • Summary & references

Thread model

The thread model your object supports depends on the requirements of your object. If your client can afford to wait until the other client finishes with you, then you don't need to read this section except for academic reasons. More often than not, you do require your object to service request from multiple clients simultaneously. The first thing you do when you think of multithreaded apartments is secure your IUnknown methods. Now, in case of ATL, IUnknown implementation is given to you. So you need to tell the ATL framework about the kind of object you are in terms of threading model.

To facilitate appropriate IUnknown implementation, ATL provides you the following classes:


class CComSingleThreadModel
{
	static ULONG WINAPI Increment( LPLONG p ) { return ++(*p); }
	static ULONG WINAPI Decrement( LPLONG p ) { return --(*p); }
	
	// Other declarations which I will come to in a moment...
};

class CComMultiThreadModel
{
	static ULONG WINAPI Increment( LPLONG p ) { InterlockedIncrement(p); }
	static ULONG WINAPI Decrement( LPLONG p ) { InterlockedDecrement(p); }
	
	// Other declarations which I will come to in a moment...
};

class CComMultiThreadModelNoCS
{
	static ULONG WINAPI Increment( LPLONG p ) { InterlockedIncrement(p); }
	static ULONG WINAPI Decrement( LPLONG p ) { InterlockedDecrement(p); }
	
	// Other declarations which I will come to in a moment...
};

CComSingleThreadModel implements the increment/decrement functions with simple ++/-- operators. Now since CComMultiThreadModelNoCS and CComMultiThreadModel are identical with respect to their increment/decrement operations, for now, it doesn't make any difference. I will visit the CComMultiThreadModelNoCS in a short while. Needless to say, CComXXXObject uses only these static functions to modify reference count. Ok, so this handles your reference counters. But what about others, your class specific data? Or instance data? You can use Win32 critical section objects to synchronize your access. ATL provides you with few classes for critical sections. They are:


class CComCriticalSection
{
public:
	void Lock() {EnterCriticalSection(&m_sec);}
	void Unlock() {LeaveCriticalSection(&m_sec);}
	void Init() {InitializeCriticalSection(&m_sec);}
	void Term() {DeleteCriticalSection(&m_sec);}
	CRITICAL_SECTION m_sec;
};

class CComAutoCriticalSection
{
public:
	void Lock() {EnterCriticalSection(&m_sec);}
	void Unlock() {LeaveCriticalSection(&m_sec);}
	CComAutoCriticalSection() {InitializeCriticalSection(&m_sec);}
	~CComAutoCriticalSection() {DeleteCriticalSection(&m_sec);}
	CRITICAL_SECTION m_sec;
};

class CComFakeCriticalSection
{
public:
	void Lock() {}
	void Unlock() {}
	void Init() {}
	void Term() {}
};

The CComAutoCriticalSection automatically initializes and deletes itself in it's constructor and destructor respectively, whereas CComCriticalSection gives more power into the hands of the user by giving him two functions, Init() and Term() for the same purpose. (Why this subtle difference results in a complete class, I will touch upon in a moment.) It is always handy to have a fake object which behaves consistently with its more serious counterparts. CComFakeCriticalSection is one such class, which just acts as a placeholder. These three classes provide you with enough combinations to handle just about any situation. The next thing is about integrating these classes with whatever we have learnt so far. The pressure is surely building up.

These classes are used in our previously discussed ThreadModel classes. Where else would a critical section be? Before going ahead, think yourself: What critical section object would each of the thread model classes, CComSingleThreadModel, CComMultiThreadModel and CComMultiThreadModelNoCS require? Single thread model requires no critical section. Multithreaded object would require a critical section to safeguard its instance data. CComMultiThreadModelNoCS is for situations where your object intends to reside in a multithreaded apartment, but does not require any locking for it's data members (apart from ref count, which is taken care of by the Increment and Decrement functions). Otherwise, you will incur the cost of entering and leaving these critical sections unnecessarily. Keeping these points in mind, we can now complete the thread model classes:


class CComSingleThreadModel // Complete implementation
{
public:
	static ULONG WINAPI Increment(LPLONG p) {return ++(*p);}
	static ULONG WINAPI Decrement(LPLONG p) {return --(*p);}
	typedef CComFakeCriticalSection AutoCriticalSection;
	typedef CComFakeCriticalSection CriticalSection;
	typedef CComSingleThreadModel ThreadModelNoCS;
};

class CComMultiThreadModel // Complete implementation
{
public:
	static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}
	static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}
	typedef CComAutoCriticalSection AutoCriticalSection;
	typedef CComCriticalSection CriticalSection;
	typedef CComMultiThreadModelNoCS ThreadModelNoCS;
};

class CComMultiThreadModelNoCS // Complete implementation
{
public:
	static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}
	static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}
	typedef CComFakeCriticalSection AutoCriticalSection;
	typedef CComFakeCriticalSection CriticalSection;
	typedef CComMultiThreadModelNoCS ThreadModelNoCS;
};

Ok, let us sum up what we have learnt so far. ATL provides three critical section classes, one whose initialization and deletion is bound to the lifetime of the CS object, one whose initialization and deletion is at the user's hand, and one to fake a critical section which fools the user by doing nothing and being there only for code consistency. CComSingleThreadModel and CComMultiThreadModel (and CComMultiThreadModelNOCS) classes pertain to single thread model and multi thread models respectively. CComMultiThreadModelNoCS class is for that chance situation where your class is a not-very-complicated one enough to require instance specific safeguarding but intends to reside in an MTA (and hence needs to safeguard it's m_refCount member). The different thread model classes use these CS objects according to their requirement. Specifically, CComSingleThreadModel and CComMultiThreadModelNOCS typedef their critical sections to CComFakeCriticalSection because they do not intend to use critical sections and CComMultiThreadModel uses the other two CS classes for it's automatic and normal CS.

The reason for having two classes CComAutoCriticalSection and CComCriticalSection is when you need to have a global (or static) critical section and you do not link the C Runtime library (which does automatic construction and destruction of global objects). So, if your constructor/destructor does not run, how will the CS be initialized and deleted? Since there is no C Runtime library, you have do this yourself, using the Init() and Term() functions. (Note that C Runtime library is responsible only for construction/destruction of global and static scoped objects.) It's as simple as that.

Now to wrap it up properly, ATL provides two typedefs that are conditionally compiled to give you the most efficient manipulation of global and object variables. These are: CComObjectThreadModel and CComGlobalsThreadModel. These are typedef'd to either to CComSingleThreadModel or CComMultiThreadModel depending on which threading model your module uses. They are declared like this:


#if defined(_ATL_SINGLE_THREADED)
	typedef CComSingleThreadModel CComObjectThreadModel;
	typedef CComSingleThreadModel CComGlobalsThreadModel;
#elif defined(_ATL_APARTMENT_THREADED)
	typedef CComSingleThreadModel CComObjectThreadModel;
	typedef CComMultiThreadModel CComGlobalsThreadModel;
#else
	typedef CComMultiThreadModel CComObjectThreadModel;
	typedef CComMultiThreadModel CComGlobalsThreadModel;
#endif

So, if your object lies in only Main STA (_ATL_SINGLE_THREADED), both these typedef to CComSingleThreadModel because all your objects lie in one STA and hence need not synchronize access to anything. If your object can lie in any STA (_ATL_APARTMENT_THREADED), it object thread model is CComSingleThreadModel and global thread model is CComMultiThreadModel because your object can be instantiated in any STA and hence needs to sync it's access to global data. Otherwise, both are typedefed to CComMultiThreadModel which means total safety to your object. CComObjectThreadModel, whatever it typedefs to, is passed to CComObjectRootEx as a template parameter while creation of your object. CComGlobalsThreadModel is used by the CComModule class and the CComClassFactory classes, which act at a global level, so that they get the threading model pertaining to global data.

Most of the things discussed above are for people like me who want to know things because they are there and, in this case, they are beautiful. If you want to be a normal ATL user and are merely interested in internals, it's ok. Whatever you have understood is probably more than enough. For the other more curious ones, write code and use these typedefs, classes, defines and see for yourself, how beautiful they really are.

Dual interface support

So, ok, you want to make your interface to be accessible by both vtable based clients and automation clients. You need to implement IDispatch. So you derive your interface IX from IDispatch and implement the four IDispatch functions in your COM object. And implementing IDispatch in your class is not something that ATL cannot do for you! So ATL gives you IDispatchImpl (along with other Impl classes). The pieces of code that will go into your COM object if you were to implement IDispatch yourself are:

In the constructor:


IID* 	pIID	= &IID_IX; // Your dispatch interface.
GUID* 	pLIBID	= &LIBID_COMServerLib; // Your typelibray's GUID.
WORD	wMajor	= 1; // Major version of your typelibrary.
WORD	wMinor	= 0; // Minor version of your typelibrary.

// Now you need to load the type library and get the type library interface.
ITypeLib* pTLB = 0;

HRESULT hr = LoadRegTypeLib( *pLIBID, wMajor, wMinor, 0, &pTLB );

// Go ahead with the type library. Get ITypeInfo interface etc.

And you need to implement the four dreaded functions of IDispatch. So how will ATL go about it? Template of course. We will look at IDispatchImpl, which is given in ATLCOM.H.


template <class T, const IID* piid, const GUID* plibid = &CComModule::m_libid,
	WORD wMajor = 1,
WORD wMinor = 0, class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE IDispatchImpl : public T
{
public:
	typedef tihclass _tihclass;
// IDispatch
	STDMETHOD(GetTypeInfoCount)(UINT* pctinfo)
	{
		*pctinfo = 1;
		return S_OK;
	}
	STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
	{
		return _tih.GetTypeInfo(itinfo, lcid, pptinfo);
	}
	STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
		LCID lcid, DISPID* rgdispid)
	{
		return _tih.GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
	}
	STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid,
		LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
		EXCEPINFO* pexcepinfo, UINT* puArgErr)
	{
		return _tih.Invoke((IDispatch*)this, dispidMember, riid, lcid,
		wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
	}
protected:
	static _tihclass _tih;
	static HRESULT GetTI(LCID lcid, ITypeInfo** ppInfo)
	{
		return _tih.GetTI(lcid, ppInfo);
	}
};

template <class T, const IID* piid, const GUID* plibid, WORD wMajor,
	WORD wMinor, class tihclass>
IDispatchImpl<T, piid, plibid, wMajor, wMinor, tihclass>::_tihclass
IDispatchImpl<T, piid, plibid, wMajor, wMinor, tihclass>::_tih =
{piid, plibid, wMajor, wMinor, NULL, 0, NULL, 0};

There are quite a few interesting points to note in the above implementation. One of those is _tihclass. In most cases, CComTypeInfoHolder is the main class that holds the type information. A variable of type CComTypeInfoHolder is created (_tih) through the _tihclass typedef. So do not get bothered by the gory code here. Most of it is for you, (god forbid) if you decide to change your type info holder to something other than CComTypeInfoHolder. The four lines below the class is nothing but definition of the static variable _tih (which is a member of CComTypeInfoHolder) that is initialized with the template parameters. So you see, you provide the necessary parameters to your IDispatchImpl through template arguments. IDispatchImpl stores these values in CComTypeInfoHolder. It's GetTI() function loads the type library, gets the type info. And once you have the type info, implementing the IDispatch functions is trivial. You can find the implementation of CComTypeInfoHolder in ATLCOM.H.

Object activation (Standalone and Aggregation)

ATL provides two classes for the two types of object activation COM supports: Standalone and Aggregated. Standalone activation is taken care of by CComObject class which is defined something like this:


template <class Base>
class CComObject : public Base
{
public:
	typedef Base _BaseClass;
	CComObject(void* = NULL)
	{
		_Module.Lock();
	}
	// Set refcount to 1 to protect destruction
	~CComObject()
	{
		m_dwRef = 1L;
		FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
		_Module.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
		_Module.Unlock();
	}
	//If InternalAddRef or InternalRelease is undefined then your class
	//doesn't derive from CComObjectRoot
	STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
	STDMETHOD_(ULONG, Release)()
	{
		ULONG l = InternalRelease();
		if (l == 0)
			delete this;
		return l;
	}
	//if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
	STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
	{return _InternalQueryInterface(iid, ppvObject);}
	template <class Q>
	HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
	{
		return QueryInterface(__uuidof(Q), (void**)pp);
	}

	static HRESULT WINAPI CreateInstance(CComObject<Base>** pp);
};

The functions InternalAddRef() and InternalRelease() are defined in CComObjectRootEx which do nothing but call the Increment/Decrement function of ThreadModel (remember about Interlocked... and ++ etc. in the Thread model section?). The _InternalQueryInterface() function is a little interesting here. Where does it's definition come from? It is defined in your COM_MAP. It is the one that calls InternalQueryInterface() of CComObjectRootBase class and passes him the _ATL_INTMAP_ENTRY array for table driven QI(). Another shorter version of QI() provided here is QueryInterface(Q** pp) which uses the __uuidof declaration specifier to get the IID of the interface to be queried for.

Aggregated activation is achieved by means of the CComAggObject class which is very interesting and looks like this:


template <class contained>
class CComAggObject :
	public IUnknown,
	public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{
public:
	typedef contained _BaseClass;
	CComAggObject(void* pv) : m_contained(pv)
	{
		_Module.Lock();
	}
	//If you get a message that this call is ambiguous then you need to
	// override it in your class and call each base class' version of this
	HRESULT FinalConstruct()
	{
		CComObjectRootEx<contained::_ThreadModel::ThreadModelNoCS>::FinalConstruct();
		return m_contained.FinalConstruct();
	}
	void FinalRelease()
	{
		CComObjectRootEx<contained::_ThreadModel::ThreadModelNoCS>::FinalRelease();
		m_contained.FinalRelease();
	}
	// Set refcount to 1 to protect destruction
	~CComAggObject()
	{
		m_dwRef = 1L;
		FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
		_Module.DeleteNonAddRefThunk(this);
#endif
		_Module.Unlock();
	}

	STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
	STDMETHOD_(ULONG, Release)()
	{
		ULONG l = InternalRelease();
		if (l == 0)
			delete this;
		return l;
	}
	STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
	{
		HRESULT hRes = S_OK;
		if (InlineIsEqualUnknown(iid))
		{
			if (ppvObject == NULL)
				return E_POINTER;
			*ppvObject = (void*)(IUnknown*)this;
			AddRef();
#ifdef _ATL_DEBUG_INTERFACES
			_Module.AddThunk((IUnknown**)ppvObject,
			(LPCTSTR)contained::_GetEntries()[-1].dw, iid);
#endif // _ATL_DEBUG_INTERFACES
		}
		else
			hRes = m_contained._InternalQueryInterface(iid, ppvObject);
		return hRes;
	}
	template <class Q>
	HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
	{
		return QueryInterface(__uuidof(Q), (void**)pp);
	}
	static HRESULT WINAPI CreateInstance(LPUNKNOWN pUnkOuter,
	CComAggObject<contained>** pp)
	{
		ATLASSERT(pp != NULL);
		HRESULT hRes = E_OUTOFMEMORY;
		CComAggObject<contained>* p = NULL;
		ATLTRY(p = new CComAggObject<contained>(pUnkOuter))
		if (p != NULL)
		{
			p->SetVoid(NULL);
			p->InternalFinalConstructAddRef();
			hRes = p->FinalConstruct();
			p->InternalFinalConstructRelease();
			if (hRes != S_OK)
			{
				delete p;
				p = NULL;
			}
		}
		*pp = p;
		return hRes;
	}

	CComContainedObject<contained> m_contained;
};

NOTE: The other aspects of the above classes will be discussed in the next section.

CComAggObject works quite differently from CComObject. First and foremost, it is NOT derived from your class. It contains your class through CComContainedObject class. CComContainedObject class has mainly three IUnknown functions, calls to which it delegates to m_pOuterUnknown that is a member of CComObjectRootBase and is set in the CComContainedObject constructor (the outer unknown, as you see above, is passed to m_contained in the CComAggObject constructor). So, CComContainedObject (with your class as template parameter) acts as your delegating unknown.

Also, CComAggObject derives from IUnknown. Yes, you guessed it right. It is to give the implementation of the non-delegating unknown. Hence, the three IUnknown functions of this class are implemented in pretty much the same manner as CComObject. For this purpose and for getting m_pOuterUnknown, CComAggObject also derives from CComObjectRootEx. So CComAggObject is a kind of parallel object to yours to provide the non-delegating unknown functionality. CComContainedObject looks like this:


template <class Base> //Base must be derived from CComObjectRoot
class CComContainedObject : public Base
{
}; // For full implementation. you can refer to ATLCOM.H.

CComContainedObject has a member function named GetControllingUnknown() which returns the outer unknown (m_pOuterUnknown). This is used by one of the creator classes (Refer my previous article) to pass outer unknown to CoCreateInstance() while creating your object as an aggregate.

m_pOuterUnknown and m_dwRef, the ref counting of the object, are kept by CComObjectRootBase as a union. The reason for this is that CComAggObject part of your COM object (non-delegating) uses the m_dwRef part of this union as it does not need the outer unknown pointer and CComContainedObject part of your COM object uses the m_pOuterUnknown part (delegating) as it needs the outer unknown part and does not need the ref counting as that is being kept by the CComAggObject part. Another optimization provided by ATL.

Let us see the memory requirements with each of these implementations. For standalone activation, you have one ref count variable and one IUnknown implementation. For aggregated activation, you have one ref count variable (from CComAggObject), one outer unknown pointer (from CComContainedObject) and two IUnknown implementations. This is fine as this would be the minimum requirement for you to create COM object in both the cases, respectively. Now if you happen to activate your COM object in both ways, you will have two classes due to template initialization: CComAggObject<CObject> and CComObject<CObject>. So, you have two vtables. By tweaking CComAggObject just a little bit, you can make it work for standalone activation. Just change the constructor a little:


CComPolyObject(void* pv) : m_contained(pv ? pv : this)
{
	_Module.Lock();
}

The name of the tweaked CComAggObject class is CComPolyObject. It checks the pv parameter for null and if it is (standalone), it sets m_contained to this, which means m_contained thinks the outer unknown is CComPolyObject and calls it's unknown functions (which are non-delegating). Otherwise, it works exactly like CComAggObject. So, you have only one class; hence, one vtable. This, of course, means that your standalone activation becomes a little more costlier than the plain CComObject implementation because of the unnecessary unknown implementation and a pointer to it. So, use it when the number of standalone instances are far less than aggregated instances.

Error handling while object creation

CreateInstance returns HRESULT. But constructors do not return anything. What if your constructors failed? In the absence of C runtime library, you cannot throw an exception. So, what do you do? The solution to this is not very elegant but definitely works. We have two functions FinalContruct() and FinalRelease() which ATL's Creator classes call after renewing any object. It returns the much required HRESULT. So, any heavy duty code should go in these functions rather than constructors and destructors.


HRESULT FinalContruct();
HRESULT FinalRelease();

FinalContruct() is called after every new instance of any ATL object. At that point, the ref count on the object is 0. Now, if you happen to call QI() for some interface inside FinalConstruct(), you indirectly do an AddRef(). So your ref count becomes 1. After QIing, if you call Release() on the object, the ref count becomes 0 and the object gets destroyed in FinalConstruct() itself! To avoid this, ATL lets you implement InternalFinalConstructAddRef() and InternalFinalConstructRelease() which, by default (in CComObjectRootBase) do nothing (which means you will fall in the above trap). If you want to do the AddRef() (ie. protect the FinalConstruct), use the DECLARE_PROTECT_FINAL_CONSTRUCT macro:


#define DECLARE_PROTECT_FINAL_CONSTRUCT()\
	void InternalFinalConstructAddRef() {InternalAddRef();}\
	void InternalFinalConstructRelease() {InternalRelease();}

So now it is in your hands when to implement these two functions. Just declare this macro in your class. Actually, the object wizard defines this macro for you. So your FinalConstruct() is always protected. The same argument goes for FinalRelease() as well. ATL calls FinalRelease() from the derived most classes' destructors where ref count is 0. If FinalRelease() needs to QI(), ref count will become 1, and Release() will make the ref count 0, resulting in call to destructor again! To avoid this, the destructors of these classes artificially set the ref count to 1 and then call FinalRelease() so that destructor does not get called more than once.

Summary & references

Now that you have completed reading it, start again. One other way to do it is, open ATLCOM.H and ATLBASE.H and read them like a non-edited novel. For doubts, turn to these two articles as they, pretty much, sum up whatever you might be interested in while beginning with ATL. If nothing works, refer to books like ATL Internals and Beginning ATL COM Programming and join the ATL mailing list at discuss.microsoft.com.


Power your site with idr newswire

Contribute to IDR:

To contribute an article to IDR, a click here.

To contact us at IDevResource.com, use our feedback form, or email us.

To comment on the site contact our webmaster.

Promoted by CyberSavvy UK - website promotion experts

All content © Copyright 2000 IDevResource.com, Disclaimer notice

Learn C#

Join the Developers Webring

Visit the IDR Forums

WTL Introduction

Visit the IDR Forums