ATLInternals II
ATLInternals II
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).
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.
class CComSingleThreadModel
{
static ULONG WINAPI Increment( LPLONG p ) { return ++(*p); }
static ULONG WINAPI Decrement( LPLONG p ) { return --(*p); }
class CComMultiThreadModel
{
static ULONG WINAPI Increment( LPLONG p )
{ InterlockedIncrement(p); }
static ULONG WINAPI Decrement( LPLONG p )
{ InterlockedDecrement(p); }
1
Copyright 2000 iDevResource.com Ltd.
class CComMultiThreadModelNoCS
{
static ULONG WINAPI Increment( LPLONG p )
{ InterlockedIncrement(p); }
static ULONG WINAPI Decrement( LPLONG p )
{ InterlockedDecrement(p); }
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() {}
};
2
Copyright 2000 iDevResource.com Ltd.
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:
3
Copyright 2000 iDevResource.com Ltd.
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.
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
4
Copyright 2000 iDevResource.com Ltd.
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.
In the constructor:
// Now you need to load the type library and get the type library interface.
ITypeLib* pTLB = 0;
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.
5
Copyright 2000 iDevResource.com Ltd.
}
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.
6
Copyright 2000 iDevResource.com Ltd.
7
Copyright 2000 iDevResource.com Ltd.
CComObjectRootEx<contained::_ThreadModel::ThreadModelNoCS>::FinalConstru
ct();
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();
}
8
Copyright 2000 iDevResource.com Ltd.
CComContainedObject<contained> m_contained;
};
NOTE: The other aspects of the above classes will be discussed in the next section.
9
Copyright 2000 iDevResource.com Ltd.
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:
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:
10
Copyright 2000 iDevResource.com Ltd.
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.
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.
11
Copyright 2000 iDevResource.com Ltd.
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.
Good luck!
12