0% found this document useful (0 votes)
42 views12 pages

ATLInternals II

Uploaded by

dweqrqr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
42 views12 pages

ATLInternals II

Uploaded by

dweqrqr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 12

Copyright 2000 iDevResource.com Ltd.

ATL - Architecture and Internals (part 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).

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


};

1
Copyright 2000 iDevResource.com Ltd.

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

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:

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;
};

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.

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

4
Copyright 2000 iDevResource.com Ltd.

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;

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.

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:

6
Copyright 2000 iDevResource.com Ltd.

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.

7
Copyright 2000 iDevResource.com Ltd.

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>::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();
}

STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}


STDMETHOD_(ULONG, Release)()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{

8
Copyright 2000 iDevResource.com Ltd.

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

9
Copyright 2000 iDevResource.com Ltd.

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

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.

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

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

You might also like