Chapter 4. Objects in ATL
ATL’s fundamental support
for COM can be split into two pieces: objects and servers. This
chapter covers classes and concentrates on how IUnknown
is
implemented as related to threading and various COM identity
issues, such as standalone versus aggregated objects. The next
chapter focuses on how to expose classes from COM servers.
Implementing IUnknown
A COM object has one responsibility: to
implement the methods of IUnknown
. Those methods perform
two services, lifetime management and runtime type discovery, as
follows:
1interface IUnknown {
2 // runtime type discovery
3 HRESULT QueryInterface([in] REFIID riid,
4 [out, iid_is(riid)] void **ppv);
5
6 // lifetime management
7 ULONG AddRef();
8 ULONG Release();
9}
COM allows every object to implement these methods as it chooses (within certain restrictions, as described in Chapter 5, “COM Servers”). The canonical implementation is as follows:
1// Server lifetime management
2extern void ServerLock();
3extern void ServerUnlock();
4
5class CPenguin : public IBird, public ISnappyDresser {
6public:
7 CPenguin() : m_cRef(0) { ServerLock(); }
8 virtual ~CPenguin() { ServerUnlock(); }
9
10 // IUnknown methods
11 STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {
12 if( riid == IID_IBird || riid == IID_IUnknown )
13 *ppv = static_cast<IBird*>(this);
14 else if( riid == IID_ISnappyDresser )
15 *ppv = static_cast<ISnappyDresser*>(this);
16 else *ppv = 0;
17
18 if( *ppv ) {
19 reinterpret_cast<IUnknown*>(*ppv)->AddRef();
20 return S_OK;
21 }
22
23 return E_NOINTERFACE;
24 }
25
26 ULONG AddRef()
27 { return InterlockedIncrement(&m_cRef); }
28
29 ULONG Release() {
30 ULONG l = InterlockedDecrement(&m_cRef);
31 if( l == 0 ) delete this;
32 return l;
33 }
34
35 // IBird and ISnappyDresser methods...
36private:
37 ULONG m_cRef;
38};
This implementation of IUnknown
is
based on several assumptions:
The object is heap-based because it removes itself using the
delete
operator. Furthermore, the object’s outstanding references completely govern its lifetime. When it has no more references, it deletes itself.The object is capable of living in a multithread apartment because it manipulates the reference count in a thread-safe manner. Of course, the other methods must be implemented in a thread-safe manner as well for the object to be fully thread safe.
The object is standalone and cannot be aggregated because it does not cache a reference to a controlling outer, nor does it forward the methods of
IUnknown
to a controlling outer.The object exposes its interfaces using multiple inheritance.
The existence of the object keeps the server running. The constructor and the destructor are used to lock and unlock the server, respectively.
These common assumptions are not the only possibilities. Common variations include the following:
An object can be global and live for the life of the server. Such objects do not need a reference count because they never delete themselves.
An object might not need to be thread safe because it might be meant to live only in a single-threaded apartment.
An object can choose to allow itself to be aggregated as well as, or instead of, supporting standalone activation.
An object can expose interfaces using other techniques besides multiple inheritance, including nested composition, tear-offs, and aggregation.
You might not want the existence of an object to force the server to keep running. This is common for global objects because their mere existence prohibits the server from unloading.
Changing any of these assumptions results in a
different implementation of IUnknown
, although the rest of
the object’s implementation is unlikely to change much (with the
notable exception of thread safety). These implementation details
of IUnknown
tend to take a very regular form and can be
encapsulated into C++ classes. Frankly, we’d really like to use
someone else’s tested code and be able to change our minds later
without a great deal of effort. We’d also like this boilerplate
code to be easily separated from the actual behavior of our objects
so that we can focus on our domain-specific implementation. ATL was
designed from the ground up to provide just this kind of
functionality and flexibility.
The Layers of ATL
ATL’s support for building COM objects is separated into several layers, as shown in Figure 4.1.
Figure 4.1. The layers of ATL

These layers break down into services exposed by ATL for building objects:
CComObjectRootEx
uses aCComXxxThreadModel
to provide “just thread-safe enough” object lifetime management and object locking.CComObjectRootBase
andCComObjectRootEx
provide helper functions used in implementingIUnknown
.Your class, which derives from
CComObjectRootEx
, also derives from any interfaces it wants to implement, as well as providing the method implementations. You or one of the ATLIXxxImpl
classes can provide method implementations.CComObject
et al, provides the actual implementation of the methods ofIUnknown
in a way consistent with your desires for object and server lifetime management requirements. This final layer actually derives from your class.
Your choice of base classes and the most derived
class determines the way the methods of IUnknown
are
implemented. If your choices change, using different classes at
compile time (or runtime) will change how ATL implements IUnknown
, independently of the rest of the behavior of
your object. The following sections explore each layer of ATL.
Threading Model Support
Just Enough Thread Safety
The
thread-safe implementation of AddRef
and Release
shown previously might be overkill for your COM objects. For
example, if instances of a specific class will live in only a
single-threaded apartment, there’s no reason to use the thread-safe
Win32 functions InterlockedIncrement
and InterlockedDecrement
. For single-threaded objects, the
following implementation of AddRef
and Release
is
more efficient:
1class Penquin {
2...
3 ULONG AddRef()
4 { return ++m_cRef; }
5
6 ULONG Release() {
7 ULONG l = m_cRef;
8 if( l == 0 ) delete this;
9 return l;
10 }
11...
12};
Using the thread-safe Win32 functions also works
for single-threaded objects, but unnecessary thread safety requires
extra overhead. For this reason, ATL provides three classes, CComSingleThreadModel
, CComMultiThreadModel
, and CComMultiThreadModelNoCS
. These classes provide two static
member functions, Increment
and Decrement
, for
abstracting away the differences between managing an object’s
lifetime count in a multithreaded manner versus a single-threaded
one. The two versions of these functions are as follows (notice
that both CComMultiThreadModel
and CComMultiThreadModelNoCS
have identical implementations of
these functions):
1class CComSingleThreadModel {
2 static ULONG WINAPI Increment(LPLONG p) { return ++(*p); }
3 static ULONG WINAPI Decrement(LPLONG p) { return (*p); }
4 ...
5};
6
7class CComMultiThreadModel {
8 static ULONG WINAPI Increment(LPLONG p) { return InterlockedIncrement(p); }
9 static ULONG WINAPI Decrement(LPLONG p) { return InterlockedDecrement(p); }
10 ...
11};
12class CComMultiThreadModelNoCS {
13 static ULONG WINAPI Increment(LPLONG p) { return InterlockedIncrement(p); }
14 static ULONG WINAPI Decrement(LPLONG p) { return InterlockedDecrement(p); }
15 ...
16};
Using these classes, you can
parameterize [1] the class to give a “just thread-safe
enough”AddRef
and Release
implementation:
1template <typename ThreadModel>
2class Penquin {
3...
4 ULONG AddRef()
5 { return ThreadModel::Increment(&m_cRef); }
6
7 ULONG Release() {
8 ULONG l = ThreadModel::Decrement(&m_cRef);
9 if( l == 0 ) delete this;
10 return l;
11 }
12...
13};
Now, based on our requirements for the CPenguin
class, we can make it just thread-safe enough by
supplying the threading model class as a template parameter:
1// Let's make a thread-safe CPenguin
2CPenguin* pobj = new CPenguin<CComMultiThreadModel>( );
Instance Data Synchronization
When you create a thread-safe object, protecting the object’s reference count isn’t enough. You also have to protect the member data from multithreaded access. One popular method for protecting data that multiple threads can access is to use a Win32 critical section object, as shown here:
1template <typename ThreadModel>
2class CPenguin {
3public:
4 CPenguin() {
5 ServerLock();
6 InitializeCriticalSection(&m_cs);
7 }
8
9 ~CPenguin() { ServerUnlock(); DeleteCriticalSection(&m_cs); }
10
11 // IBird
12 STDMETHODIMP get_Wingspan(long* pnWingspan) {
13 Lock(); // Lock out other threads during data read
14 *pnWingSpan = m_nWingspan;
15 Unlock();
16 return S_OK;
17 }
18
19 STDMETHODIMP put_Wingspan(long nWingspan) {
20 Lock(); // Lock out other threads during data write
21 m_nWingspan = nWingspan;
22 Unlock();
23 return S_OK;
24 }
25 ...
26private:
27 CRITICALSECTION m_cs;
28
29 void Lock() { EnterCriticalSection(&m_cs); }
30 void Unlock() { LeaveCriticalSection(&m_cs); }
31};
Notice that before
reading or writing any member data, the CPenguin
object
enters the critical section, locking out access by other threads.
This coarse-grained, object-level locking keeps the scheduler from
swapping in another thread that could corrupt the data members
during a read or a write on the original thread. However,
object-level locking doesn’t give you as much concurrency as you
might like. If you have only one critical section per object, one
thread might be blocked trying to increment the reference count
while another is updating an unrelated member variable. A greater
degree of concurrency requires more critical sections, allowing one
thread to access one data member while a second thread accesses
another. Be careful using this kind of finer-grained
synchronization – it often leads to deadlock:
1class CZax : public IZax {
2public:
3 ...
4 // IZax
5 STDMETHODIMP GoNorth() {
6 EnterCriticalSection(&m_cs1); // Enter cs1...
7 EnterCriticalSection(&m_cs2); // ...then enter cs2
8 // Go north...
9 LeaveCriticalSection(&m_cs2);
10 LeaveCriticalSection(&m_cs1);
11 }
12
13 STDMETHODIMP GoSouth() {
14 EnterCriticalSection(&m_cs2); // Enter cs2...
15 EnterCriticalSection(&m_cs1); // ...then enter cs1
16 // Go south...
17 LeaveCriticalSection(&m_cs1);
18 LeaveCriticalSection(&m_cs2);
19 }
20 ...
21private:
22 CRITICAL_SECTION m_cs1;
23 CRITICAL_SECTION m_cs2;
24};
Imagine that the scheduler let the northbound Zax [2] thread enter the first critical section and then swapped in the southbound Zax thread to enter the second critical section. If this happened, neither Zax could enter the other critical section; therefore, neither Zax thread would be able to proceed. This would leave them deadlocked while the world went on without them. Try to avoid this. [3]
The Sneeches and Other Stories, by Theodor Geisel (aka Dr. Seuss).
For more guidance on what to do about deadlocks, read Win32 Multithreaded Programming (O’Reilly and Associates, 1997), by Mike Woodring and Aaron Cohen.
Whether you decide to use object-level locking
or finer-grained locking, critical sections are handy. ATL provides
four class wrappers that simplify their use:
CComCriticalSection
, CComAutoCriticalSection
, CComSafeDeleteCriticalSection
, and CComAutoDeleteCriticalSection
.
1class CComCriticalSection {
2public:
3 CComCriticalSection() {
4 memset(&m_sec, 0, sizeof(CRITICAL_SECTION));
5 }
6 ~CComCriticalSection() { }
7 HRESULT Lock() {
8 EnterCriticalSection(&m_sec);
9 return S_OK;
10 }
11
12 HRESULT Unlock() {
13 LeaveCriticalSection(&m_sec);
14 return S_OK;
15 }
16
17 HRESULT Init() {
18 HRESULT hRes = E_FAIL;
19 __try {
20 InitializeCriticalSection(&m_sec);
21 hRes = S_OK;
22 }
23 // structured exception may be raised in
24 // low memory situations
25 __except(STATUS_NO_MEMORY == GetExceptionCode()) {
26 hRes = E_OUTOFMEMORY;
27 }
28
29 return hRes;
30 }
31
32 HRESULT Term() {
33 DeleteCriticalSection(&m_sec);
34 return S_OK;
35 }
36 CRITICAL_SECTION m_sec;
37};
38
39class CComAutoCriticalSection : public CComCriticalSection {
40public:
41 CComAutoCriticalSection() {
42 HRESULT hr = CComCriticalSection::Init();
43 if (FAILED(hr))
44 AtlThrow(hr);
45 }
46 ~CComAutoCriticalSection() {
47 CComCriticalSection::Term();
48 }
49private:
50 // Not implemented. CComAutoCriticalSection::Init
51 // should never be called
52 HRESULT Init();
53 // Not implemented. CComAutoCriticalSection::Term
54 // should never be called
55 HRESULT Term();
56};
57
58class CComSafeDeleteCriticalSection
59 : public CComCriticalSection {
60public:
61 CComSafeDeleteCriticalSection(): m_bInitialized(false) { }
62
63 ~CComSafeDeleteCriticalSection() {
64 if (!m_bInitialized) { return; }
65 m_bInitialized = false;
66 CComCriticalSection::Term();
67 }
68
69 HRESULT Init() {
70 ATLASSERT( !m_bInitialized );
71 HRESULT hr = CComCriticalSection::Init();
72 if (SUCCEEDED(hr)) {
73 m_bInitialized = true;
74 }
75 return hr;
76 }
77
78 HRESULT Term() {
79 if (!m_bInitialized) { return S_OK; }
80 m_bInitialized = false;
81 return CComCriticalSection::Term();
82 }
83
84 HRESULT Lock() {
85 ATLASSUME(m_bInitialized);
86 return CComCriticalSection::Lock();
87 }
88
89private:
90 bool m_bInitialized;
91};
92
93class CComAutoDeleteCriticalSection : public CComSafeDeleteCriticalSection {
94private:
95 // CComAutoDeleteCriticalSection::Term should never be called
96 HRESULT Term() ;
97};
Notice that CComCriticalSection
does not
use its constructor or destructor to initialize and delete the
contained critical section. Instead, it contains Init
and Term
functions for this purpose. CComAutoCriticalSection
, on the other hand, is easier to
use because it automatically creates the critical section in its
constructor and destroys it in the destructor.
CComSafeDeleteCriticalSection
does half
that job; it doesn’t create the critical section until the Init
method is called, but it always deletes the critical
section (if it exists) in the destructor. You also have the option
of manually calling Term
if you want to explicitly delete
the critical section ahead of the object’s destruction. CComAutoDeleteCriticalSection
, on the other hand, blocks
the Term
method by simply declaring it but never defining
it; calling CComAutoDeleteCriticalSection::Term
gives you
a linker error. These classes were useful before ATL was consistent
about supporting construction for global and static variables, but
these classes are largely around for historical reasons at this
point; you should prefer CComAutoCriticalSection
.
Using a CComAutoCriticalSection
in our CPenguin
class simplifies the code a bit:
1template <typename ThreadModel>
2class CPenguin {
3public:
4 // IBird methods Lock() and Unlock() as before...
5...
6private:
7 CComAutoCriticalSection m_cs;
8
9 void Lock() { m_cs.Lock(); }
10 void Unlock() { m_cs.Unlock(); }
11};
Note that with both CComAutoCriticalSection
and CComCriticalSection
,
the user must take care to explicitly call Unlock
before
leaving a section of code that has been protected by a call to Lock
. In the presence of code that might throw exceptions
(which a great deal of ATL framework code now does), this can be
difficult to do because each piece of code that can throw an
exception represents a possible exit point from the function. CComCritSecLock
addresses this issue by automatically
locking and unlocking in its constructor and destructor. CComCritSecLock
is parameterized by the lock type so that
it can serve as a wrapper for CComCriticalSection
or CComAutoCriticalSection
.
1template< class TLock >
2class CComCritSecLock {
3public:
4 CComCritSecLock( TLock& cs, bool bInitialLock = true );
5 ~CComCritSecLock() ;
6
7 HRESULT Lock() ;
8 void Unlock() ;
9
10// Implementation
11private:
12 TLock& m_cs;
13 bool m_bLocked;
14...
15};
16
17template< class TLock >
18inline CComCritSecLock< TLock >::CComCritSecLock(
19 TLock& cs,bool bInitialLock )
20 : m_cs( cs ), m_bLocked( false ) {
21 if( bInitialLock ) {
22 HRESULT hr;
23 hr = Lock();
24 if( FAILED( hr ) ) { AtlThrow( hr ); }
25 }
26}
27
28template< class TLock >
29inline CComCritSecLock< TLock >::~CComCritSecLock() {
30 if( m_bLocked ) { Unlock(); }
31}
32
33template< class TLock >
34inline HRESULT CComCritSecLock< TLock >::Lock() {
35 HRESULT hr;
36 ATLASSERT( !m_bLocked );
37 hr = m_cs.Lock();
38 if( FAILED( hr ) ) { return( hr ); }
39 m_bLocked = true;
40 return( S_OK );
41}
42
43template< class TLock >
44inline void CComCritSecLock< TLock >::Unlock() {
45 ATLASSERT( m_bLocked );
46 m_cs.Unlock();
47 m_bLocked = false;
48}
If the bInitialLock
parameter to the
constructor is true, the contained critical section is locked upon
construction. In normal use on the stack, this is exactly what you
want, which is why true
is the default. However, as usual
with constructors, if something goes wrong, you don’t have an easy
way to return the failure code. If you need to know whether the
lock failed, you can pass false
instead and then call Lock
explicitly. Lock
returns the HRESULT
from the lock operation. This class ensures that
the contained critical section is unlocked whenever an instance of
this class leaves scope because the destructor automatically
attempts to call Unlock
if it detects that the instance is
currently locked.
Notice that our CPenguin
class is still
parameterized by the threading model. There’s no sense in
protecting our member variables in the single-threaded case.
Instead, it would be handy to have another critical section class
that could be used in place of CComCriticalSection
or CComAutoCriticalSection
. ATL provides the CComFakeCriticalSection
class for this purpose:
1class CComFakeCriticalSection {
2public:
3 HRESULT Lock() { return S_OK; }
4 HRESULT Unlock() { return S_OK; }
5 HRESULT Init() { return S_OK; }
6 HRESULT Term() { return S_OK; }
7};
Given CComFakeCriticalSection
, we could
further parameterize the CPenguin
class by adding another
template parameter, but this is unnecessary. The ATL threading
model classes already contain type definitions that map to a real
or fake critical section, based on whether you’re doing single or
multithreading:
1class CcomSingleThreadModel {
2public:
3 static ULONG WINAPI Increment(LPLONG p) {return ++(*p);}
4 static ULONG WINAPI Decrement(LPLONG p) {return (*p);}
5 typedef CComFakeCriticalSection AutoCriticalSection;
6 typedef CComFakeCriticalSection AutoDeleteCriticalSection;
7 typedef CComFakeCriticalSection CriticalSection;
8 typedef CComSingleThreadModel ThreadModelNoCS;
9};
10
11class CcomMultiThreadModel {
12public:
13 static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}
14 static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}
15 typedef CComAutoCriticalSection AutoCriticalSection;
16 typedef CComAutoDeleteCriticalSection
17 AutoDeleteCriticalSection;
18 typedef CComCriticalSection CriticalSection;
19 typedef CComMultiThreadModelNoCS ThreadModelNoCS;
20};
21
22class CcomMultiThreadModelNoCS {
23public:
24 static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}
25 static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}
26 typedef CComFakeCriticalSection AutoCriticalSection;
27 typedef CComFakeCriticalSection AutoDeleteCriticalSection;
28 typedef CComFakeCriticalSection CriticalSection;
29 typedef CComMultiThreadModelNoCS ThreadModelNoCS;
30};
These type definitions enable us to make the CPenguin
class just thread safe enough for both the
object’s reference count and course-grained object
synchronization:
1template <typename ThreadingModel>
2class CPenguin {
3public:
4 // IBird methods as before...
5...
6private:
7 ThreadingModel::AutoCriticalSection m_cs;
8
9 void Lock() { m_cs.Lock(); }
10 void Unlock() { m_cs.Unlock(); }
11};
This technique enables you to provide the
compiler with operations that are just thread safe enough. If the
threading model is CComSingleThreadModel
, the calls to
Increment
and Decrement
resolve to operator++
and
operator--
, and the Lock
and Unlock
calls resolve to empty inline functions.
If the threading model is CComMultiThreadModel
, the calls to Increment
and Decrement
resolve to calls to InterlockedIncrement
and InterlockedDecrement.
The Lock
and Unlock
calls resolve to calls to EnterCriticalSection
and LeaveCriticalSection
.
Finally, if the model is CComMultiThreadModelNoCS
, the calls to Increment
and Decrement
are thread safe, but the critical section is
fake, just as with CComSingleThreadModel
.
CComMultiThreadModelNoCS
is designed for multithreaded
objects that eschew object-level locking in favor of a more
fine-grained scheme. Table4.1 shows how the code is expanded based on the threading model
class you use:
Table 4.1. Expanded Code Based on Threading Model Class
|
|
|
|
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The Server’s Default Threading Model
ATL-based servers have a concept of a “default”
threading model for things that you don’t specify directly. To set
the server’s default threading model, you define one of the
following symbols: _ATL_SINGLE_THREADED
,
_ATL_APARTMENT_THREADED
, or _ATL_FREE_THREADED
.
If you don’t specify one of these symbols, ATL assumes _ATL_FREE_THREADED
. However, the ATL Project Wizard
defines _ATL_APARTMENT_THREADED
in the generated stdafx.h
file. ATL uses these symbols to define two type
definitions:
1#if defined(_ATL_SINGLE_THREADED)
2...
3 typedef CComSingleThreadModel CComObjectThreadModel;
4 typedef CComSingleThreadModel CComGlobalsThreadModel;
5
6#elif defined(_ATL_APARTMENT_THREADED)
7...
8 typedef CComSingleThreadModel CComObjectThreadModel;
9 typedef CComMultiThreadModel CComGlobalsThreadModel;
10
11#elif defined(_ATL_FREE_THREADED)
12...
13 typedef CComMultiThreadModel CComObjectThreadModel;
14 typedef CComMultiThreadModel CComGlobalsThreadModel;
15...
16#endif
Internally, ATL uses CComObjectThreadModel
to protect instance data and CComGlobalsThreadModel
to protect global and static data.
Because the usage is difficult to override in some cases, you
should make sure that ATL is compiled using the most protective
threading model of any of the classes in your server. In practice,
this means you should change the wizard-generated _ATL_APARTMENT_THREADED
symbol to _ATL_FREE_THREADED
if you have even one multithreaded
class in your server.
The Core of IUnknown
Standalone Reference Counting
To encapsulate the Lock
and Unlock
methods as well as the “just thread-safe enough”
reference counting, ATL provides the CComObjectRootEx
base class, parameterized by the desired threading model [4] :
As an
optimization, ATL provides a specialization of CComObjectRootEx<CComSingleThreadModel>
that does
not have a _CritSec
object, side-stepping minimum object
size requirements imposed by the compiler.
1template <class ThreadModel>
2class CComObjectRootEx : public CComObjectRootBase {
3public:
4 typedef ThreadModel _ThreadModel;
5 typedef typename _ThreadModel::AutoCriticalSection _CritSec;
6 typedef typename _ThreadModel::AutoDeleteCriticalSection _AutoDelCritSec;
7 typedef CComObjectLockT<_ThreadModel> ObjectLock;
8
9 ~CComObjectRootEx() {}
10
11 ULONG InternalAddRef() {
12 ATLASSERT(m_dwRef != -1L);
13 return _ThreadModel::Increment(&m_dwRef);
14 }
15 ULONG InternalRelease() {
16#ifdef _DEBUG
17 LONG nRef = _ThreadModel::Decrement(&m_dwRef);
18 if (nRef < -(LONG_MAX / 2)) {
19 ATLASSERT(0 &&
20 _T("Release called on a pointer that has"
21 " already been released"));
22 }
23 return nRef;
24#else
25 return _ThreadModel::Decrement(&m_dwRef);
26#endif
27 }
28
29 HRESULT _AtlInitialConstruct() { return m_critsec.Init(); }
30 void Lock() {m_critsec.Lock();}
31 void Unlock() {m_critsec.Unlock();}
32private:
33 _AutoDelCritSec m_critsec;
34};
35
36template <>
37class CComObjectRootEx<CComSingleThreadModel>
38 : public CComObjectRootBase {
39public:
40 typedef CComSingleThreadModel _ThreadModel;
41 typedef _ThreadModel::AutoCriticalSection _CritSec;
42 typedef _ThreadModel::AutoDeleteCriticalSection
43 _AutoDelCritSec;
44 typedef CComObjectLockT<_ThreadModel> ObjectLock;
45
46 ~CComObjectRootEx() {}
47
48 ULONG InternalAddRef() {
49 ATLASSERT(m_dwRef != -1L);
50 return _ThreadModel::Increment(&m_dwRef);
51 }
52 ULONG InternalRelease() {
53#ifdef _DEBUG
54 long nRef = _ThreadModel::Decrement(&m_dwRef);
55 if (nRef < -(LONG_MAX / 2)) {
56 ATLASSERT(0 && _T("Release called on a pointer "
57 "that has already been released"));
58 }
59 return nRef;
60#else
61 return _ThreadModel::Decrement(&m_dwRef);
62#endif
63 }
64
65 HRESULT _AtlInitialConstruct() { return S_OK; }
66
67 void Lock() {}
68 void Unlock() {}
69};
ATL classes derive from CComObjectRootEx
and forward AddRef
and Release
calls to the InternalAddRef
and InternalRelease
methods when the object is created
standalone (that is, not aggregated). Note that
InternalRelease
checks the decremented reference count
against the somewhat odd-looking value (LONG_MAX / 2)
. The
destructor of CComObject
(or one of its alternatives,
discussed a bit later) sets the reference count to this value. The
ATL designers could have used a different value here, but basing
the value on LONG_MAX
makes it unlikely that such a
reference count could be reached under normal circumstances.
Dividing LONG_MAX
by 2 ensures that the resulting value
can’t mistakenly be reached by wrapping around from 0. InternalRelease
simply checks the reference count against
this value to see if you’re trying to call Release
on an
object that has already been destroyed. If so, an assert is issued
in debug builds.
The template specialization for CComSingleThreadModel
demonstrates the “just safe enough”
multithreading. When used in a single-threaded object, the Lock
and Unlock
methods do nothing, and no
critical section object is created.
With the Lock
and Unlock
methods so readily available in the base class, you might be
tempted to write the following incorrect code:
1class CPenguin
2 : public CComObjectRootEx<CComMultiThreadModel>, ... {
3 STDMETHODIMP get_Wingspan(long* pnWingspan) {
4 Lock();
5 if( !pnWingspan ) return E_POINTER; // Forgot to Unlock
6 *pnWingSpan = m_nWingspan;
7 Unlock();
8 return S_OK;
9 }
10 ...
11};
To help you avoid this kind of mistake, CComObjectRootEx
provides a type definition for a class
called ObjectLock
, based on CComObjectLockT
parameterized by the threading model:
1template <class ThreadModel>
2class CcomObjectLockT {
3public:
4 CComObjectLockT(CComObjectRootEx<ThreadModel>* p) {
5 if (p)
6 p->Lock();
7 m_p = p;
8 }
9
10 ~CComObjectLockT() {
11 if (m_p)
12 m_p->Unlock();
13 }
14 CComObjectRootEx<ThreadModel>* m_p;
15};
16
17template <>
18class CComObjectLockT<CComSingleThreadModel> {
19public:
20 CComObjectLockT(CComObjectRootEx<CComSingleThreadModel>*) {}
21 ~CComObjectLockT() {}
22};
Instances of CComObjectLockT
Lock
the object passed to the constructor
and Unlock
it upon destruction. The ObjectLock
type definition provides a convenient way to write code that will
properly release the lock regardless of the return path:
1class CPenguin
2 : public CComObjectRootEx<CComMultiThreadModel>, ... {
3 STDMETHODIMP get_Wingspan(long* pnWingspan) {
4 ObjectLock lock(this);
5 if( !pnWingspan ) return E_POINTER; // Unlock happens as
6 // stack unwinds
7 *pnWingSpan = m_nWingspan;
8 return S_OK;
9 }
10 ...
11};
Of course, the specialization for CComSingleThreadModel
ensures that in the single-threaded
object, no locking is done. This is useful when you’ve changed your
threading model; you don’t pay a performance penalty for using an ObjectLock
if you don’t actually need one.
Table-Driven QueryInterface
In addition to “just
thread-safe enough” implementations of AddRef
and Release
for standalone COM objects, CComObjectRootEx
(via its base class, CComObjectRootBase
) provides a static, table-driven
implementation of QueryInterface
called InternalQueryInterface
:
1static HRESULT WINAPI
2CComObjectRootBase::InternalQueryInterface(
3 void* pThis,
4 const _ATL_INTMAP_ENTRY* pEntries,
5 REFIID iid,
6 void** ppvObject);
This function’s job is to use the this
pointer of the object, provided as the pThis
parameter,
and the requested interface to fill the ppvObject
parameter with a pointer to the appropriate virtual function table
pointer (vptr
). It does this using the pEntries
parameter, a zero-terminated array of _ATL_INTMAP_ENTRY
structures:
1struct _ATL_INTMAP_ENTRY {
2 const IID* piid;
3 DWORD dw;
4 _ATL_CREATORARGFUNC* pFunc;
5};
Each interface exposed from a COM object is one
entry in the interface map, which is a class static array of _ATL_INTMAP_ENTRY
structures. Each entry consists of an
interface identifier, a function pointer, and an argument for the
function represented as a DWORD
. This provides a flexible,
extensible mechanism for implementing QueryInterface
that
supports multiple inheritance, aggregation, tear-offs, nested
composition, debugging, chaining, and just about any other wacky
COM identity tricks C++ programmers currently use. [5] However, because most interfaces are implemented using multiple
inheritance, you don’t often need this much flexibility. For
example, consider one possible object layout for instances of the CPenguin
class, shown in Figure 4.2.
Chapter 5, “COM Servers,” describes all these uses of the interface map.
Figure 4.2. CPenguin object layout, including ``vptr`` s to ``vtbl`` s

1class CPenguin : public IBird, public ISnappyDresser {...};
The typical
implementation of QueryInterface
for a class using
multiple inheritance consists of a series of if
statements
and static_cast
operations; the purpose is to adjust the this
pointer by some fixed offset to point to the
appropriate vptr
. Because the offsets are known at compile
time, a table matching interface identifiers to offsets would
provide an appropriate data structure for adjusting the this
pointer at runtime. To support this common case,
InternalQueryInterface
function treats the _ATL_INTMAP_ENTRY
as a simple IID/offset pair if the pFunc
member has the special value _ATL_SIMPLEMAPENTRY
:
1#define _ATL_SIMPLEMAPENTRY ((ATL::_ATL_CREATORARGFUNC*)1)
To be able to use the InternalQueryInterface
function, each implementation
populates a static interface map. To facilitate populating this
data structure, and to provide some other methods used internally,
ATL provides the following macros (as well as others described in Chapter 6, “Interface
Maps”):
1#define BEGIN_COM_MAP(class) ...
2#define COM_INTERFACE_ENTRY(itf) ...
3#define END_COM_MAP() ...
For example, our CPenguin
class would
declare its interface map like this:
1class CPenguin :
2 public CComObjectRootEx<CComMultiThreadModel>,
3 public IBird,
4 public ISnappyDresser {
5...
6public:
7BEGIN_COM_MAP(CPenguin)
8 COM_INTERFACE_ENTRY(IBird)
9 COM_INTERFACE_ENTRY(ISnappyDresser)
10END_COM_MAP()
11...
12};
In an abbreviated form, this would expand to the following:
1class CPenguin :
2 public CComObjectRootEx<CComMultiThreadModel>,
3 public IBird,
4 public ISnappyDresser {
5...
6public:
7 IUnknown* GetUnknown() {
8 ATLASSERT(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY);
9 return (IUnknown*)((int)this+_GetEntries()->dw); }
10 }
11 HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) {
12 return InternalQueryInterface(this, _GetEntries(), iid, ppvObject);
13 }
14 const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() {
15 static const _ATL_INTMAP_ENTRY _entries[] = {
16 { &_ATL_IIDOF(IBird), 0, _ATL_SIMPLEMAPENTRY },
17 { &_ATL_IIDOF(ISnappyDresser), 4, _ATL_SIMPLEMAPENTRY },
18 { 0, 0, 0 }
19 };
20 return _entries;
21 }
22...
23};
The _ATL_IIDOF
macro expands as
follows:
1#ifndef _ATL_NO_UUIDOF
2#define _ATL_IIDOF(x) __uuidof(x)
3#else
4#define _ATL_IIDOF(x) IID_##x
5#endif
This macro lets you choose to use __uuidof
operator or the standard naming convention to
specify the IID for the interface in question for the entire
project.
Figure4.3 shows how this interface map relates to an instance of a CPenguin
object in memory.
Figure 4.3. ``CPenguin`` interface map, ``CPenguin`` object, and vtbls

Something else worth mentioning is the GetUnknown
member function that the BEGIN_COM_MAP
provides. Although ATL uses this internally, it’s also useful when
passing your this
pointer to a function that requires an IUnknown*
. Because your class derives from potentially
more than one interface, each of which derives from IUnknown
, the compiler considers passing your own this
pointer as an IUnknown*
to be ambiguous.
1HRESULT FlyInAnAirplane(IUnknown* punkPassenger);
2
3// Penguin.cpp
4STDMETHODIMP CPenguin::Fly() {
5 return FlyInAnAirplane(this); // ambiguous
6}
For these situations, GetUnknown is your friend, e.g.
1STDMETHODIMP CPenguin::Fly() {
2 return FlyInAnAirplane(this->GetUnknown()); // unambiguous
3}
As you’ll see later, GetUnknown
is
implemented by handing out the first entry in the interface
map.
Support for Aggregation: The Controlled Inner
So far, we’ve discussed the implementation of IUnknown
for standalone COM objects. However, if our
object is to participate in aggregation as a controlled inner,our job is not to think for ourselves, but
rather to be subsumed by the thoughts and prayers of another. A
controlled inner does this by blindly forwarding all calls on the
publicly available implementation of IUnknown
to the
controlling outer’s implementation. The controlling outer’s
implementation is provided as the pUnkOuter
argument to
the CreateInstance
method of IClassFactory
. If
our ATL-based COM object is used as a controlled inner, it simply
forwards all calls to IUnknown
methods to the OuterQueryInterface
, OuterAddRef
, and OuterRelease
functions provided in CComObjectRootBase
; these, in turn, forward to the
controlling outer. The relevant functions of CComObjectRootBase
are shown here:
1class CComObjectRootBase {
2public:
3 CComObjectRootBase() { m_dwRef = 0L; }
4 ...
5 ULONG OuterAddRef() {
6 return m_pOuterUnknown->AddRef();
7 }
8 ULONG OuterRelease() {
9 return m_pOuterUnknown->Release();
10 }
11 HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject) {
12 return m_pOuterUnknown->QueryInterface(iid, ppvObject);
13 }
14 ...
15 union {
16 long m_dwRef;
17 IUnknown* m_pOuterUnknown;
18 };
19};
Notice that CComObjectRootBase
keeps
the object’s reference count and a pointer to a controlling unknown
as a union. This implies that an object can either maintain its own
reference count or be aggregated,
but not both at the same time. This implication is not true. If the
object is being aggregated, it must maintain a reference count and a pointer to a controlling
unknown. In this case, discussed more later, ATL keeps the m_pUnkOuter
in one instance of the CComObjectBase
and derives from CComObjectBase
again to keep the object’s
reference count.
More to Come
Although it’s possible to implement the methods
of IUnknown
directly in your class using the methods of
the base class CComObjectRootEx
, most ATL classes don’t.
Instead, the actual implementations of the IUnknown
methods are left to a class that
derives from your class, as in CComObject
.
We discuss this after we talk about the responsibilities of your
class.
Your Class
Because ATL provides the behavior for IUnknown
in the CComObjectRootEx
class and
provides the actual implementation in the CComObject
(and
friends) classes, the job your class performs is pretty simple:
derive from interfaces and implement their methods. Besides making
sure that the interface map lists all the interfaces you’re
implementing, you can pretty much leave implementing IUnknown
to ATL and concentrate on your custom
functionality. This is, after all, the whole point of ATL in the
first place.
ATL’s Implementation Classes
Many standard interfaces have common
implementations. ATL provides implementation classes of many
standard interfaces. For example, IPersistImpl
,
IConnectionPointContainerImpl
, and IViewObjectExImpl
implement IPersist
,
IConnectionPointContainer
, and IViewObjectEx
,
respectively. Some of these interfaces are common enough that many
objects can implement them; for example, persistence, eventing, and
enumeration. Some are more special purpose and related only to a
particular framework, as with controls, Internet-enabled
components, and Microsoft Management Console extensions. Most of
the general-purpose interface implementations are discussed in Chapters 7,
“Persistence in ATL”; 8, “Collections and Enumerators”; and 9, “Connection
Points.” The interface implementations related to the controls
framework are discussed in Chapters 10, “Windowing,” and 11, “ActiveX Controls.” One
implementation is general purpose enough to discuss right here: IDispatchImpl
.
Scripting Support
For a scripting environment to access
functionality from a COM object, the COM object must implement IDispatch
:
1interface IDispatch : IUnknown {
2 HRESULT GetTypeInfoCount([out] UINT * pctinfo);
3
4 HRESULT GetTypeInfo([in] UINT iTInfo,
5 [in] LCID lcid,
6 [out] ITypeInfo ** ppTInfo);
7
8 HRESULT GetIDsOfNames([in] REFIID riid,
9 [in, size_is(cNames)] LPOLESTR * rgszNames,
10 [in] UINT cNames,
11 [in] LCID lcid,
12 [out, size_is(cNames)] DISPID * rgDispId);
13
14 HRESULT Invoke([in] DISPID dispIdMember,
15 [in] REFIID riid,
16 [in] LCID lcid,
17 [in] WORD wFlags,
18 [in, out] DISPPARAMS * pDispParams,
19 [out] VARIANT * pVarResult,
20 [out] EXCEPINFO * pExcepInfo,
21 [out] UINT * puArgErr);
22}
The most important methods of IDispatch
are GetIDsOfNames
and Invoke
. Imagine the
following line of scripting code:
1penguin.wingspan = 102
This translates into two calls on IDispatch
. The first is GetIDsOfNames
, which asks
the object if it supports the wingspan
property. If the
answer is yes, the second call to IDispatch
is to Invoke
. This call includes an identifier (called a DISPID
) that uniquely identifies the name of the property
or method the client is interested in (as retrieved from GetIDsOfName
s), the type of operation to perform (calling
a method, or getting or setting a property), a list of arguments,
and a place to put the result (if any). The object’s implementation
of Invoke
is then required to interpret the request the
scripting client made. This typically involves unpacking the list
of arguments (which is passed as an array of VARIANT
structures), converting them to the appropriate types (if
possible), pushing them onto the stack, and calling some other
method implemented that deals in real data types, not VARIANT
s. In theory, the object’s implementation could
take any number of interesting, dynamic steps to parse and
interpret the client’s request. In practice, most objects forward
the request to a helper, whose job it is to build a stack and call
a method on an interface implemented by the object to do the real
work. The helper makes use of type information held in a type
library typically bundled with the server. COM type libraries hold
just enough information to allow an instance of a TypeInfo
object – that is, an object that implements ITypeInfo
– to
perform this service. The TypeInfo
object used to
implement IDispatch
is usually based on a dual interface,
defined in IDL like this:
1[ object, dual, uuid(44EBF74E-116D-11D2-9828-00600823CFFB) ]
2interface IPenguin : IDispatch {
3 [propput] HRESULT Wingspan([in] long nWingspan);
4 [propget] HRESULT Wingspan([out, retval] long* pnWingspan);
5 HRESULT Fly();
6}
Using a TypeInfo
object as a helper allows an
object to implement IDispatch
like this (code in bold
indicates differences between one implementation and another):
1class CPenguin :
2 public CComObectRootEx<CComSingleThreadModel>,
3 public IBird,
4 public ISnappyDresser,
5 public IPenguin {
6public:
7 CPenguin() : m_pTypeInfo(0) {
8 IID* pIID = &IID_IPenguin;
9 GUID* pLIBID = &LIBID_BIRDSERVERLib;
10 WORD wMajor = 1;
11 WORD wMinor = 0;
12 ITypeLib* ptl = 0;
13 HRESULT hr = LoadRegTypeLib(*pLIBID, wMajor, wMinor,
14 0, &ptl);
15 if( SUCCEEDED(hr) ) {
16 hr = ptl->GetTypeInfoOfGuid(*pIID, &m_pTypeInfo);
17 ptl->Release();
18 }
19 }
20
21 virtual ~Penguin() {
22 if( m_pTypeInfo ) m_pTypeInfo->Release();
23 }
24
25BEGIN_COM_MAP(CPenguin)
26 COM_INTERFACE_ENTRY(IBird)
27 COM_INTERFACE_ENTRY(ISnappyDresser)
28 COM_INTERFACE_ENTRY(IDispatch)
29 COM_INTERFACE_ENTRY(IPenguin)
30END_COM_MAP()
31
32 // IDispatch methods
33 STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {
34 return (*pctinfo = 1), S_OK;
35 }
36 STDMETHODIMP GetTypeInfo(UINT ctinfo, LCID lcid,
37 ITypeInfo **ppti) {
38 if( ctinfo != 0 ) return (*ppti = 0), DISP_E_BADINDEX;
39 return (*ppti = m_pTypeInfo)->AddRef(), S_OK;
40 }
41
42 STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames,
43 UINT cNames, LCID lcid, DISPID *rgdispid) {
44 return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames,
45 rgdispid);
46 }
47
48 STDMETHODIMP Invoke(DISPID dispidMember,
49 REFIID riid,
50 LCID lcid,
51 WORD wFlags,
52 DISPPARAMS *pdispparams,
53 VARIANT *pvarResult,
54 EXCEPINFO *pexcepinfo,
55 UINT *puArgErr) {
56 return m_pTypeInfo->Invoke(static_cast<IPenguin*>(this),
57 dispidMember, wFlags,
58 pdispparams, pvarResult,
59 pexcepinfo, puArgErr);
60 }
61 // IBird, ISnappyDresser and IPenguin methods...
62private:
63 ITypeInfo* m_pTypeInfo;
64};
Because this implementation is so boilerplate
(it varies only by the dual interface type, the interface
identifier, the type library identifier, and the major and minor
version numbers), it can be easily implemented in a template base
class. ATL’s parameterized implementation of IDispatch
is IDispatchImpl
:
1template <class T,
2 const IID* piid = &__uuidof(T),
3 const GUID* plibid = &CAtlModule::m_libid,
4 WORD wMajor = 1,
5 WORD wMinor = 0,
6 class tihclass = CComTypeInfoHolder>
7class ATL_NO_VTABLE IDispatchImpl : public T {...};
Given IDispatchImpl
, our IPenguin
implementation gets quite a bit simpler:
1class CPenguin :
2 public CComObjectRootEx<CComMultiThreadModel>,
3 public IBird,
4 public ISnappyDresser,
5 public IDispatchImpl<IPenguin, &IID_IPenguin> {
6public:
7BEGIN_COM_MAP(CPenguin)
8 COM_INTERFACE_ENTRY(IBird)
9 COM_INTERFACE_ENTRY(ISnappyDresser)
10 COM_INTERFACE_ENTRY(IDispatch)
11 COM_INTERFACE_ENTRY(IPenguin)
12END_COM_MAP()
13 // IBird, ISnappyDresser and IPenguin methods...
14};
Supporting Multiple Dual Interfaces
I wish it wouldn’t, but this question always comes up: “How do I support multiple dual interfaces in my COM objects?” My answer is always, “Why would you want to?”
The problem is, of the scripting environments
I’m familiar with that require an object to implement IDispatch
, not one supports QueryInterface
. So
although it’s possible to use ATL to implement multiple dual
interfaces, you have to choose which implementation to hand out as
the “default” – that is, the one the client gets when asking for IDispatch
.
For example, let’s say that instead of having a
special IPenguin
interface that represents the full
functionality of my object to scripting clients, I decided to make
all the interfaces dual interfaces.
1[ dual, uuid(...) ] interface IBird : IDispatch {...}
2[ dual, uuid(...) ] interface ISnappyDresser : IDispatch { ... };
You can implement both of these dual interfaces
using ATL’s IDispatchImpl
:
1class CPenguin :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public IDispatchImpl<IBird, &IID_IBird>,
4 public IDispatchImpl<ISnappyDresser, &IID_ISnappyDresser> {
5public:
6BEGIN_COM_MAP(CPenguin)
7 COM_INTERFACE_ENTRY(IBird)
8 COM_INTERFACE_ENTRY(ISnappyDresser)
9 COM_INTERFACE_ENTRY(IDispatch) // ambiguous
10END_COM_MAP()
11...
12};
However, when you fill in the interface map in this
way, the compiler gets upset. Remember that the COM_INTERFACE_ENTRY
macro essentially boils down to a static_cast
to the interface in question. Because two
different interfaces derive from IDispatch
, the compiler
cannot resolve the one to which you’re trying to cast. To resolve
this difficulty, ATL provides another macro:
1#define COM_INTERFACE_ENTRY2(itf, branch)
This macro enables you to tell the compiler
which branch to follow up the inheritance hierarchy to the IDispatch
base. Using this macro allows you to choose the
default IDispatch
interface:
1class CPenguin :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public IDispatchImpl<IBird, &IID_IBird>,
4 public IDispatchImpl<ISnappyDresser, &IID_ISnappyDresser> {
5public:
6BEGIN_COM_MAP(CPenguin)
7 COM_INTERFACE_ENTRY(IBird)
8 COM_INTERFACE_ENTRY(ISnappyDresser)
9 COM_INTERFACE_ENTRY2(IDispatch, IBird) // Compiles
10 // (unfortunately)
11END_COM_MAP()
12...
13};
That brings me to my objection. Just because ATL
and the compiler conspire to allow this usage doesn’t mean that
it’s a good one. There is no good reason to support multiple dual
interfaces on a single implementation. Any client that supports QueryInterface
will not need to use GetIDsOfNames
or Invoke
. These kinds of clients are perfectly happy
using a custom interface, as long as it matches their argument type
requirements. On the other hand, scripting clients that don’t
support QueryInterface
can get to methods and properties
on only the default dual interface. For example, the following will
not work:
1// Since IBird is the default, its operations are available
2penguin.fly
3// Since ISnappyDresser is not the default, its operations
4// aren't available
5penguin.straightenTie // runtime error
So, here’s my advice: Don’t design your reusable,
polymorphic COM interfaces as dual interfaces. Instead, if you’re
going to support scripting clients, define a single dual interface
that exposes the entire functionality of the class, as I did when
defining IPenguin
in the first place. As an added benefit,
this means that you have to define only one interface that supports
scripting clients instead of mandating that all of them do.
Having said that, sometimes you don’t have a
choice. For example, when building Visual Studio add-ins, you need
to implement two interfaces: _IDTExtensibility2
and IDTCommandTarget
. Both of these are defined as dual
interfaces, so the environment forces you to deal with this
problem. [6] You’ll need to look at the documentation
and do some experimentation to figure out which of your IDispatch
implementations should be the default.
The
annoying thing here is that Visual Studio never even calls the IDispatch
side
of these dual interfaces, so making them dual interfaces was
completely unnecessary.
CComObject Et Al
Consider the following C++ class:
1class CPenguin :
2 public CComObjectRootEx<CComMultiThreadModel>,
3 public IBird,
4 public ISnappyDresser {
5public:
6BEGIN_COM_MAP(CPenguin)
7 COM_INTERFACE_ENTRY(IBird)
8 COM_INTERFACE_ENTRY(ISnappyDresser)
9END_COM_MAP()
10 // IBird and ISnappyDresser methods...
11 // IUnknown methods not implemented here
12};
Because this class doesn’t implement the methods
of IUnknown
, the following will fail at compile time:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter,
3 REFIID riid, void** ppv) {
4 ...
5 CPenguin* pobj = new CPenguin; // IUnknown not implemented
6 ...
7}
Given CComObjectRootBase
, you can easily implement the methods
of IUnknown
:
1// Server lifetime management
2extern void ServerLock();
3extern void ServerUnlock();
4
5class CPenguin :
6 public CComObjectRootEx<CComMultiThreadModel>,
7 public IBird,
8 public ISnappyDresser {
9public:
10 CPengin() { ServerLock(); }
11 ~CPenguin() { ServerUnlock(); }
12BEGIN_COM_MAP(CPenguin)
13 COM_INTERFACE_ENTRY(IBird)
14 COM_INTERFACE_ENTRY(ISnappyDresser)
15END_COM_MAP()
16 // IBird and ISnappyDresser methods...
17 // IUnknown methods for standalone, heap-based objects
18 STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
19 { return _InternalQueryInterface(riid, ppv); }
20
21 STDMETHODIMP_(ULONG) AddRef()
22 { return InternalAddRef(); }
23
24 STDMETHODIMP_(ULONG) Release() {
25 ULONG l = InternalRelease();
26 if( l == 0 ) delete this;
27 return l;
28 }
29};
Unfortunately, although this implementation does leverage the base class behavior, it has hard-coded assumptions about the lifetime and identity of our objects. For example, instances of this class can’t be created as an aggregate. Just as we’re able to encapsulate decisions about thread safety into the base class, we would like to encapsulate decisions about lifetime and identity. However, unlike thread-safety decisions, which are made on a per-class basis and are, therefore, safe to encode into a base class, lifetime and identity decisions can be made on a per-instance basis. Therefore, we’ll want to encapsulate lifetime and identity behavior into classes meant to derive from our class.
Standalone Activation
To encapsulate the standalone, heap-based object
implementation of IUnknown
I just showed you, ATL provides CComObject
, shown in a slightly abbreviated form here:
1template <class Base>
2class CComObject : public Base {
3public:
4 typedef Base _BaseClass;
5 CComObject(void* = NULL)
6 { _pAtlModule->Lock(); } // Keeps server loaded
7
8 // Set refcount to -(LONG_MAX/2) to protect destruction and
9 // also catch mismatched Release in debug builds
10 ~CComObject() {
11 m_dwRef = -(LONG_MAX/2);
12 FinalRelease();
13#ifdef _ATL_DEBUG_INTERFACES
14 _AtlDebugInterfacesModule.DeleteNonAddRefThunk(
15 _GetRawUnknown());
16#endif
17 _pAtlModule->Unlock(); // Allows server to unload
18 }
19 STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
20 STDMETHOD_(ULONG, Release)() {
21 ULONG l = InternalRelease();
22 if (l == 0) delete this;
23 return l;
24 }
25
26 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
27 {return _InternalQueryInterface(iid, ppvObject);}
28
29 template <class Q>
30 HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
31 { return QueryInterface(__uuidof(Q), (void**)pp); }
32
33 static HRESULT WINAPI CreateInstance(CComObject<Base>** pp) ;
34};
Notice that CComObject
takes a template
parameter called Base
. This is the base class from which CComObject
derives to obtain the functionality of CComObjectRootEx
, as well as whatever custom functionality
we’d like to include in our objects. Given the implementation of CPenguin
that did not include the implementation of the IUnknown
methods, the compiler would be happy with CComObject
used as follows (although I describe later why new
shouldn’t be used directly when creating ATL-based COM
objects):
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 *ppv = 0;
5 if( pUnkOuter ) return CLASS_E_NOAGGREGATION;
6 // Read on for why not to use new like this!
7 CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
8 if( pobj ) {
9 pobj->AddRef();
10 HRESULT hr = pobj->QueryInterface(riid, ppv);
11 pobj->Release();
12 return hr;
13 }
14 return E_OUTOFMEMORY;
15}
Besides the call to FinalRelease
and
the static member function CreateInstance
(which are both
described in the “Creators” section of this
chapter), CComObject
provides one additional item of note,
the QueryInterface
member function template [7] :
For a brief description of member function templates, see Appendix A, “C++ Templates by Example.”
1template <class Q>
2HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
3{ return QueryInterface(__uuidof(Q), (void**)pp); }
This member function template uses the
capability of the VC++ compiler to tag a type with a universally
unique identifier (UUID). This capability has been available since
VC++ 5.0 and takes the form of a declarative specifier
(declspec
s):
1struct __declspec(uuid("00000000-0000-0000-C000-000000000046") IUnknown
2{...};
These declspec
specifiers are output by the Microsoft IDL compiler and are
available for both standard and custom interfaces. You can retrieve
the UUID of a type using the __uuidof
operator, allowing
the following syntax:
1void TryToFly(IUnknown* punk) {
2 IBird* pbird = 0;
3 if( SUCCEEDED(punk->QueryInterface(__uuidof(pbird),
4 (void**)&pbird) ) {
5 pbird->Fly();
6 pbird->Release();
7 }
8}
Using the QueryInterface
member
function template provided in CComObject
offers a bit more
syntactic convenience, given a CComObject
-based object
reference:
1void TryToFly(CComObject<CPenguin>* pPenguin) {
2 IBird* pbird = 0;
3 if( SUCCEEDED(pPenguin->QueryInterface(&pbird) ) {
4 pbird->Fly();
5 pbird->Release();
6 }
7}
Aggregated Activation
Notice that the CPenguin
class object
implementation shown previously disallowed aggregation by checking
for a nonzero pUnkOuter
and returning CLASS_E_NOAGGREGATION
. If we want to support aggregation
as well as – or instead of – standalone activation, we need another class
to implement the forwarding behavior of aggregated instances. For
this, ATL provides CComAggObject
.
CComAggObject
performs the chief
service of being a controlled inner – that is, providing two
implementations of IUnknown
. One implementation forwards
calls to the controlling outer, subsumed by its lifetime and
identity. The other implementation is for private use of the
controlling outer for actually maintaining the lifetime of and
querying interfaces from the inner. To obtain the two
implementations of IUnknown
, CComAggObject
derives from CComObjectRootEx
twice, once directly and
once indirectly via a contained instance of your class derived from CComContainedObject
, as shown here:
1template <class contained>
2class CComAggObject :
3 public IUnknown,
4 public CComObjectRootEx<
5 contained::_ThreadModel::ThreadModelNoCS> {
6public:
7 typedef contained _BaseClass;
8 CComAggObject(void* pv) : m_contained(pv)
9 { _pAtlModule->Lock(); }
10
11 ~CComAggObject() {
12 m_dwRef = -(LONG_MAX/2);
13 FinalRelease();
14 _pAtlModule->Unlock();
15 }
16
17 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
18 ATLASSERT(ppvObject != NULL);
19 if (ppvObject == NULL)
20 return E_POINTER;
21 *ppvObject = NULL;
22
23 HRESULT hRes = S_OK;
24 if (InlineIsEqualUnknown(iid)) {
25 *ppvObject = (void*)(IUnknown*)this;
26 AddRef();
27 }
28 else
29 hRes = m_contained._InternalQueryInterface(iid,
30 ppvObject);
31 return hRes;
32 }
33
34 STDMETHOD_(ULONG, AddRef)()
35 { return InternalAddRef(); }
36
37 STDMETHOD_(ULONG, Release)() {
38 ULONG l = InternalRelease();
39 if (l == 0) delete this;
40 return l;
41 }
42
43 template <class Q>
44 HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
45 { return QueryInterface(__uuidof(Q), (void**)pp); }
46 static HRESULT WINAPI CreateInstance(LPUNKNOWN pUnkOuter,
47 CComAggObject<contained>** pp);
48
49 CComContainedObject<contained> m_contained;
50};
You can see that instead of deriving from your
class (passed as the template argument), CComAggObject
derives directly from CComObjectRootEx
. Its implementation
of QueryInterface
relies on the interface map you’ve built
in your class, but its implementation of AddRef
and Release
access relies on the second instance of CComObjectRootBase
it gets by deriving from CComObjectRootEx
. This second instance of CComObjectRootBase
uses the m_dwRef
member of the
union.
The first instance of CComObjectRootBase
, the one that manages the m_pOuterUnknown
member of the union, is the one CComAggObject
gets by creating an instance of your class
derived from CComContainedObject
as the m_contained
data member. CComContainedObject
implements QueryInterface
, AddRef
, and Release
by delegating to the m_pOuterUnknown
passed to the constructor:
1template <class Base>
2class CComContainedObject : public Base {
3public:
4 typedef Base _BaseClass;
5 CComContainedObject(void* pv) {
6 m_pOuterUnknown = (IUnknown*)pv;
7 }
8
9 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
10 return OuterQueryInterface(iid, ppvObject);
11 }
12
13 STDMETHOD_(ULONG, AddRef)()
14 { return OuterAddRef(); }
15
16 STDMETHOD_(ULONG, Release)()
17 { return OuterRelease(); }
18
19 template <class Q>
20 HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
21 { return QueryInterface(__uuidof(Q), (void**)pp); }
22
23 IUnknown* GetControllingUnknown()
24 { return m_pOuterUnknown; }
25};
Being the Controlled Inner
Using CComAggObject
and its two
implementations of IUnknown
, our CPenguin
class
object implementation can support either standalone or aggregated
activation without touching the CPenguin
source:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 *ppv = 0;
5 if( pUnkOuter ) {
6 CComAggObject<CPenguin>* pobj =
7 new CComAggObject<CPenguin>(pUnkOuter);
8 ...
9 }
10 else {
11 CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
12 ...
13 }
14}
This usage provides the most efficient runtime
decision making. If the object is standalone, it pays the price of
one reference count and one implementation of IUnknown
. If
it is aggregated, it pays the price of one reference count, one
pointer to the controlling outer, and two implementations of IUnknown
. However, one additional price we’re paying is
one extra set of vtbl
s. By using both CComAggObject<CPenguin>
and CComObject<CPenguin>
, we’ve created two classes and,
therefore, two sets of vtbl
s. If you’ve got a small number
of instances or nearly all your instances are aggregated, you might
want a single class that can handle both aggregated and standalone
activation, thereby eliminating one set of vtbl
s. You do
this by using CComPolyObject
in place of both CComObject
and CComAggObject
:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 *ppv = 0;
5 CComPolyObject<CPenguin>* pobj =
6 new CComPolyObject<CPenguin>(pUnkOuter);
7 ...
8}
CComPolyObject
is nearly identical to CComAggObject
, except that, in its constructor, if the pUnkOuter
is zero, it uses its second implementation of IUnknown
as the outer for the first to forward to, as
shown:
1class CComPolyObject :
2 public IUnknown,
3 public CComObjectRootEx<
4 contained::_ThreadModel::ThreadModelNoCS> {
5public:
6 ...
7 CComPolyObject(void* pv) : m_contained(pv ? pv : this) {...}
8 ...
9};
The use of CComPolyObject
saves a set of vtbl
s, so the
module size is smaller, but the price you pay for standalone
objects is getting an extra implementation of IUnknown
as
well as an extra pointer to that implementation.
Alternative Activation Techniques
Besides standalone operation, CComObject
makes certain assumptions about where the
object’s memory has been allocated from (the heap) and whether the
existence of the object should keep the server loaded (it does).
For other needs, ATL provides five more classes meant to be the
most derived class in your implementation hierarchy: CComObjectCached
, CComObjectNoLock
,
CComObjectGlobal
, CComObjectStack
, and CComObjectStackEx
.
CComObjectCached
CComObjectCached
objects implement
reference counting, assuming that you’re going to create an
instance and then hold it for the life of the server, handing out
references to it as requested. To avoid keeping the server running
forever after the cached instance is created, the boundary for
keeping the server running is a reference count of one, although
the lifetime of the object is still managed on a boundary of
zero:
1template <class Base>
2class CComObjectCached : public Base {
3public:
4 ...
5 STDMETHOD_(ULONG, AddRef)() {
6 ULONG l = InternalAddRef();
7 if (l == 2)
8 _pAtlModule->Lock();
9 return l;
10 }
11 STDMETHOD_(ULONG, Release)() {
12 ULONG l = InternalRelease();
13 if (l == 0)
14 delete this;
15 else if (l == 1)
16 _pAtlModule->Unlock();
17 return l;
18 }
19 ...
20};
Cached objects are useful for in-process class objects:
1static CComObjectCached<CPenguinCO>* g_pPenguinCO = 0;
2
3BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, void*) {
4 switch( dwReason ) {
5 case DLL_PROCESS_ATTACH:
6 g_pPenguinCO = new CComObjectCached<CPenguinCO>();
7
8 // 1st ref. **doesn't** keep server alive
9 if( g_pPenguinCO ) g_pPenguinCO->AddRef();
10 break;
11
12 case DLL_PROCESS_DETACH:
13 if( g_pPenguinCO ) g_pPenguinCO->Release();
14 break;
15 }
16 return TRUE;
17}
18
19STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
20 void** ppv) {
21 // Subsequent references do keep server alive
22 if( clsid == CLSID_Penguin && g_pPenguinCO )
23 return g_pPenguinCO->QueryInterface(riid, ppv);
24 return CLASS_E_CLASSNOTAVAILABLE;
25}
CComObjectNoLock
Sometimes you don’t want outstanding references
on your object to keep the server alive. For example, class objects
in an out-of-process server are cached in a table maintained by ole32.dll
some number of
times (it might not be one). For this reason, COM itself
manages how the lifetime of a class object affects the lifetime of its
out-of-process server using the LockServer
method of the IClassFactory
interface. For this use, ATL provides CComObjectNoLock
, whose implementation does not affect the
lifetime of the server:
1template <class Base>
2class CComObjectNoLock : public Base {
3public:
4 ...
5 STDMETHOD_(ULONG, AddRef)()
6 { return InternalAddRef(); }
7
8 STDMETHOD_(ULONG, Release)() {
9 ULONG l = InternalRelease();
10 if (l == 0) delete this;
11 return l;
12 }
13 ...
14};
No-lock objects are useful for out-of-process class objects:
1int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
2 CoInitialize(0);
3
4 CComObjectNoLock<CPenguinCO>* pPenguinCO =
5 new CComObjectNoLock<CPenguinCO>();
6 if( !pPenguinCO ) return E_OUTOFMEMORY;
7 pPenguinCO->AddRef();
8
9 DWORD dwReg;
10 HRESULT hr;
11
12 // Reference(s) cached by ole32.dll won't keep server
13 // from shutting down
14 hr = CoRegisterClassObject(CLSID_Penguin, pPenguinCO, ...,
15 &dwReg);
16 if( SUCCEEDED(hr) ) {
17 MSG msg; while( GetMessage(&msg, 0, 0, 0) ) DispatchMessage(&msg);
18 CoRevokeClassObject(dwReg);
19 pPenguinCO->Release();
20 }
21
22 CoUninitialize();
23 return hr;
24}
CComObjectGlobal
Just as it’s handy to have
an object whose existence or outstanding references don’t keep the
server alive, sometimes it’s handy to have an object whose lifetime
matches that of the server. For example, a global or static object
is constructed once when the server is loaded and is not destroyed
until after WinMain
or DllMain
has completed.
Clearly, the mere existence of a global object cannot keep the
server running, or the server could never be shut down. On the
other hand, we’d like to be able to keep the server running if
there are outstanding references to a global object. For this, we
have CComObjectGlobal
:
1template <class Base>
2class CComObjectGlobal : public Base {
3public:
4 ...
5 STDMETHOD_(ULONG, AddRef )() { return _pAtlModule->Lock(); }
6 STDMETHOD_(ULONG, Release)() { return _pAtlModule->Unlock(); }
7 ...
8};
Global objects can be used instead of cached objects for implementing in-process class objects, but they’re useful for any global or static object:
1// No references yet, so server not forced to stay alive
2static CComObjectGlobal<CPenguinCO> g_penguinCO;
3
4STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
5 void** ppv) {
6 // All references keep the server alive
7 if( clsid == CLSID_Penguin )
8 return g_penguinCO.QueryInterface(riid, ppv);
9 return CLASS_E_CLASSNOTAVAILABLE;
10}
CComObjectStack and CComObjectStackEx
Instead of using a global or static object, you
might find yourself with the urge to allocate a COM object on the
stack. ATL supports this technique with CComObjectStack
:
1template <class Base>
2class CComObjectStack : public Base {
3public:
4 ...
5 STDMETHOD_(ULONG, AddRef)()
6 { ATLASSERT(FALSE); return 0; }
7
8 STDMETHOD_(ULONG, Release)()
9 { ATLASSERT(FALSE); return 0; }
10
11 STDMETHOD(QueryInterface)(REFIID iid, void** ppvObject)
12 { ATLASSERT(FALSE); return E_NOINTERFACE; }
13 ...
14};
Based on the implementation, it should be clear
that you’re no longer doing COM. CComObjectStack
shuts up
the compiler, but you still cannot use any methods of IUnknown
, which means that you cannot pass out an
interface reference from an object on the stack. This is good
because, as with a reference to anything on the stack, as soon as
the stack goes away, the reference points at garbage. The nice
thing about ATL’s implementation of CComObjectStack
is
that it warns you at runtime that you’re doing something bad:
1void DoABadThing(IBird** ppbird) {
2 CComObjectStack<CPenguin> penguin;
3 penguin.Fly(); // Using IBird method is OK
4 penguin.StraightenTie(); // Using ISnappyDresser method
5 // also OK
6
7 // This will trigger an assert at runtime
8 penguin.QueryInterface(IID_IBird, (void**)ppbird);
9}
CComObjectStackEx
addresses the
limitations of CComObjectStack
by providing a more useful
implementation of IUnknown
:
1template <class Base>
2class CComObjectStackEx : public Base {
3public:
4 typedef Base _BaseClass;
5
6 CComObjectStackEx(void* = NULL) {
7#ifdef _DEBUG
8 m_dwRef = 0;
9#endif
10 m_hResFinalConstruct = _AtlInitialConstruct();
11 if (SUCCEEDED(m_hResFinalConstruct))
12 m_hResFinalConstruct = FinalConstruct();
13 }
14
15 virtual ~CComObjectStackEx() {
16 // This assert indicates mismatched ref counts.
17 //
18 // The ref count has no control over the
19 // lifetime of this object, so you must ensure
20 // by some other means that the object remains
21 // alive while clients have references to its interfaces.
22 ATLASSUME(m_dwRef == 0);
23 FinalRelease();
24#ifdef _ATL_DEBUG_INTERFACES
25 _AtlDebugInterfacesModule.DeleteNonAddRefThunk(
26 _GetRawUnknown());
27#endif
28 }
29
30 STDMETHOD_(ULONG, AddRef)() {
31#ifdef _DEBUG
32 return InternalAddRef();
33#else
34 return 0;
35#endif
36 }
37
38 STDMETHOD_(ULONG, Release)() {
39#ifdef _DEBUG
40 return InternalRelease();
41#else
42 return 0;
43#endif
44 }
45
46 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
47 return _InternalQueryInterface(iid, ppvObject);
48 }
49
50 HRESULT m_hResFinalConstruct;
51};
As you can see, CComObjectStackEx
permits the use of the IUnknown
methods, as long as they
are called within the scope of the CComObjectStackEx
instance. This allows methods called from within the instance scope
to treat the object as if it were a typical heap-based COM object,
as in the following:
1void PlayWithBird() {
2 CComObjectStackEx<CPenguin> penguin;
3 IBird* pBird = NULL;
4 penguin.QueryInterface(IID_IBird,
5 (void**)&pBird); // OK -> no assert
6 DoBirdTrickes(pBird);
7}
8
9void DoBirdTricks(IBird* pBird) {
10 pBird->Fly(); // IBird methods OK
11 ISnappyDresser* pPenguin = NULL;
12 pBird->QueryInterface(IID_ISnappyDresser,
13 (void**)&pPenguin); // OK
14 pPenguin->StraightenTie(); // ISnappyDresser methods OK
15 pPenguin->Release(); // OK -> no assert
16}
One from Column A, Two from Column B…
Table 4.2 shows the various identity and lifetime options ATL provides.
Table 4.2. ATL’s Identity and Lifetime Options
Class
|
Standalone
or
Aggregated
|
Heap or
Stack
|
Existence
Keeps Server
Alive
|
Extent Refs
Keep Server
Alive
|
Useful
IUnkown
Methods
|
---|---|---|---|---|---|
|
Standalone |
Heap |
Yes |
Yes |
Yes |
|
Aggregated |
Heap |
Yes |
Yes |
Yes |
|
Standalone
or
aggregated
|
Heap |
Yes |
Yes |
Yes |
|
Standalone |
Heap |
No |
Second
Reference
|
Yes |
|
Standalone |
Heap |
No |
No |
Yes |
|
Standalone |
Data seg. |
No |
Yes |
Yes |
|
Standalone |
Stack |
No |
No |
No |
|
Standalone |
Stack |
No |
No |
Yes |
ATL Creators
Multiphase Construction
As I’ve mentioned, ATL servers might not necessarily link with the CRT. However, living without the CRT can be a pain. Among other things, if you don’t have the CRT, you also don’t get C++ exceptions. That doesn’t leave you much to do in the following scenario:
1// CPenguin constructor
2CPenguin::CPenguin() {
3 HRESULT hr = CoCreateInstance(CLSID_EarthAtmosphere, 0,
4 CLSCTX_ALL, IID_IAir, (void**)&m_pAir);
5 if( FAILED(hr) ) {
6 // Can't return an error from a ctor
7 return hr;
8 // Can't throw an error without the CRT
9 throw hr;
10 // This won't help
11 OutputDebugString(__T("Help! Can't bre...\n"));
12 }
13}
The OutputDebugString
isn’t going to
notify the client that the object it just created doesn’t have the
resources it needs to survive; there’s no way to return the failure
result back to the client. This hardly seems fair because the IClassFactory
method CreateInstance
that’s
creating our objects certainly can return an HRESULT
. The
problem is having a way to hand a failure from the instance to the
class object so that it can be returned to the client. By
convention, ATL classes provide a public member function called FinalConstruct
for objects to participate in multiphase
construction:
1HRESULT FinalConstruct();
An empty implementation of the FinalConstruct
member function is provided in CComObjectRootBase
, so all ATL objects have one. Because FinalConstruct
returns an HRESULT
, now you have a
clean way to obtain the result of any nontrivial construction:
1HRESULT CPenguin::FinalConstruct() {
2 return CoCreateInstance(CLSID_EarthAtmosphere, 0, CLSCTX_ALL,
3 IID_IAir, (void**)&m_pAir);
4}
5STDMETHODIMP
6CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
7 void** ppv) {
8 *ppv = 0;
9 if( !pUnkOuter ) {
10 CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
11 if( !pobj ) return E_OUTOFMEMORY;
12 HRESULT hr = pobj->FinalConstruct();
13 if( SUCCEEDED(hr) ) ...
14 return hr;
15 }
16 ...
17}
You do have something
else to consider, though. Notice that when CreateInstance
calls FinalConstruct
, it has not yet increased the
reference count of the object. This causes a problem if, during the FinalConstruct
implementation, the object handed a
reference to itself to another object. If you think this is
uncommon, remember the pUnkOuter
parameter to the IClassFactory
method CreateInstance
. However,
even without aggregation, it’s possible to run into this problem.
Imagine the following somewhat contrived but perfectly legal
code:
1// CPenguin implementation
2HRESULT CPenguin::FinalConstruct() {
3 HRESULT hr;
4 hr = CoCreateInstance(CLSID_EarthAtmosphere, 0, CLSCTX_ALL,
5 IID_IAir, (void**)&m_pAir);
6 if( SUCCEEDED(hr) ) {
7 // Pass reference to object with reference count of 0
8 hr = m_pAir->CheckSuitability(GetUnknown());
9 }
10 return hr;
11}
12
13// CEarthAtmosphere implementation in separate server
14STDMETHODIMP CEarthAtmosphere::CheckSuitability(IUnknown* punk) {
15 IBreatheO2* pbo2 = 0;
16 HRESULT hr = E_FAIL;
17
18 // CPenguin's lifetime increased to 1 via QI
19 hr = punk->QueryInterface(IID_IBreatheO2, (void**)&pbo2);
20 if( SUCCEEDED(hr) ) {
21 pbo2->Release(); // During this call, lifetime decreases
22 // to 0 and destruction sequence begins...
23 }
24
25 return (SUCCEEDED(hr) ? S_OK : E_FAIL);
26}
To avoid the problem of
premature destruction, you need to artificially increase the
object’s reference count before FinalConstruct
is called
and then decrease its reference count afterward:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 *ppv = 0;
5 if( !pUnkOuter ) {
6 CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
7 if( FAILED(hr) ) return E_OUTOFMEMORY;
8
9 // Protect object from pre-mature destruction
10 pobj->InternalAddRef();
11 hr = pobj->FinalConstruct();
12 pobj->InternalRelease();
13
14 if( SUCCEEDED(hr) ) ...
15 return hr;
16 }
17 ...
18}
Just Enough Reference Count Safety
Arguably, not all objects need their reference
count artificially managed in the way just described. In fact, for
multithreaded objects that don’t require this kind of protection,
extra calls to InterlockedIncrement
and InterlockedDecrement
represent unnecessary overhead.
Toward that end, CComObjectRootBase
provides a pair of
functions just for bracketing the call to FinalConstruct
in a “just reference count safe enough” way:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 *ppv = 0;
5 if( !pUnkOuter ) {
6 CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
7 if( FAILED(hr) ) return E_OUTOFMEMORY;
8
9 // Protect object from pre-mature destruction (maybe)
10 pobj->InternalFinalConstructAddRef();
11 hr = pobj->FinalConstruct();
12 pobj->InternalFinalConstructRelease();
13
14 if( SUCCEEDED(hr) ) ...
15 return hr;
16 }
17 ...
18}
By
default, InternalFinalConstructAddRef
and InternalFinalConstructRelease
incur no release build
runtime overhead:
1class CComObjectRootBase {
2public:
3 ...
4 void InternalFinalConstructAddRef() {}
5 void InternalFinalConstructRelease() {
6 ATLASSERT(m_dwRef == 0);
7 }
8 ...
9};
To change the implementation of InternalFinalConstructAddRef
and InternalFinalConstructRelease
to provide reference count
safety, ATL provides the following macro:
1#define DECLARE_PROTECT_FINAL_CONSTRUCT() \
2 void InternalFinalConstructAddRef() { InternalAddRef(); } \
3 void InternalFinalConstructRelease() { InternalRelease(); }
The DECLARE_PROTECT_FINAL_CONSTRUCT
macro is used on a per-class basis to turn on reference count
safety as required. Our CPenguin
would use it like
this:
1class CPenguin : ... {
2public:
3 HRESULT FinalConstruct();
4 DECLARE_PROTECT_FINAL_CONSTRUCT()
5 ...
6};
In my opinion, DECLARE_PROTECT_FINAL_CONSTRUCT
is one ATL optimization
too many. Using it requires not only a great deal of knowledge of
COM and ATL internals, but also a great deal of knowledge of how to
implement the objects you create in FinalConstruct
methods. Because you often don’t have that knowledge, the only safe
thing to do is to always use DECLARE_PROTECT_FINAL_CONSTRUCT
if you’re handing out
references to your instances in your FinalConstruct
calls.
And because that rule is too complicated, most folks will probably
forget it. So here’s a simpler one:
Every class that implements the FinalConstruct
member function
should also have a DECLARE_PROTECT_FINAL_CONSTRUCT
macro instantiation.
Luckily, the wizard generates DECLARE_PROTECT_FINAL_CONSTRUCT
when it generates a new
class, so your FinalConstruct
code will be safe by
default. If you decide you don’t want it, you can remove
it. [8]
As my friend Tim Ewald always says, “Subtractive coding is easier than additive coding.”
Another Reason for Multiphase Construction
Imagine a plain-vanilla C++ class that wants to call a virtual member function during its construction, and another C++ class that overrides that function:
1class Base {
2public:
3 Base() { Init(); }
4 virtual void Init() {}
5};
6
7class Derived : public Base {
8public:
9 virtual void Init() {}
10};
Because it’s fairly uncommon to call virtual
member functions as part of the construction sequence, it’s not
widely known that the Init
function during the constructor
for Base
will not be Derived::Init
, but Base::Init
. This might seem counterintuitive, but the
reason it works this way is a good one: It doesn’t make sense to
call a virtual member function in a derived class until the derived
class has been properly constructed. However, the derived class
isn’t properly constructed until after the base class has been
constructed. To make sure that only functions of properly
constructed classes are called during construction, the C++
compiler lays out two vtbl
s, one for Base
and one
for Derived
. The C++ runtime then adjusts the vptr
to point to the appropriate vtbl
during the
construction sequence.
Although this is all part of the official C++
standard, it’s not exactly intuitive, especially because it is so
rarely used (or maybe it’s so rarely used because it’s
unintuitive). Because it’s rarely used, beginning with Visual C++
5.0, Microsoft introduced __declspec(novtable)
to turn off
the adjustment of vptr
s during construction. If the base
class is an abstract base class, this often results in vtbl
s that are generated by the compiler but not used, so
the linker can remove them from the final image.
This optimization is used in ATL whenever a
class is declared using the ATL_NO_VTABLE
macro:
1#ifdef _ATL_DISABLE_NO_VTABLE
2#define ATL_NO_VTABLE
3#else
4#define ATL_NO_VTABLE __declspec(novtable)
5#endif
Unless the _ATL_DISABLE_NO_VTABLE
is
defined, a class defined using _ATL_NO_VTABLE
has its
constructor behavior adjusted with __declspec(novtable)
:
1class ATL_NO_VTABLE CPenguin ... {};
This is a good and true optimization, but
classes that use it must not call virtual member functions in their
constructors. [9] If virtual member functions need to be
called during construction, leave them until the call to FinalConstruct
, which is called after the most derived
class’s constructor and after the vptr
s are adjusted to
the correct values.
Strictly speaking, the compiler will statically bind to virtual calls made in the constructor or the destructor. But if a statically bound function calls a dynamically bound function, you’re still in big trouble.
One last thing should be mentioned about __declspec(novatble)
. Just as it turns off the adjustment
of vptr
s during construction, it turns off the adjustment
of vptr
s during destruction. Therefore, avoid calling
virtual functions in the destructor as well; instead, call them in
the object’s FinalRelease
member function.
FinalRelease
ATL calls the object’s FinalRelease
function after the object’s final interface reference is released
and before your ATL-based object’s destructor is called:
1void FinalRelease();
The FinalRelease
member function is useful for calling virtual
member functions and releasing interfaces to other objects that
also have pointers back to you. Because those other objects might
want to query for an interface during its shutdown sequence, it’s
just as important to protect the object against double destruction
as it was to protect it against premature destruction in FinalConstruct
. Even though the FinalRelease
member function is called when the object’s reference count has
been decreased to zero (which is why the object is being
destroyed), the caller of FinalRelease
artificially sets
the reference count to -(LONG_MAX/2)
to avoid double
deletion. The caller of FinalRelease
is the destructor of
the most derived class:
1CComObject::~CComObject() {
2 m_dwRef = -(LONG_MAX/2);
3 FinalRelease();
4 _AtlModule->Unlock();
5}
Under the Hood
Just as two-phase construction applies to code
you need to call to set up your objects, the ATL framework itself
often needs to do operations at construction time that might fail.
For example, creation of a lock object could fail for some reason.
To handle this, ATL and CComObjectRootBase
define a couple
other entry points:
1class CComObjectRootBase {
2public:
3 ...
4 // For library initialization only
5 HRESULT _AtlFinalConstruct() {
6 return S_OK;
7 }
8 ...
9 void _AtlFinalRelease() {} // temp
10};
These methods exist so that ATL has a place to
put framework-initialization functions that aren’t affected by your
work in FinalConstruct
. In addition to these methods, CComObjectRootEx
defines this setup method:
1template <class ThreadModel>
2class CComObjectRootEx : public CComObjectRootBase {
3public:
4 ...
5 HRESULT _AtlInitialConstruct() {
6 return m_critsec.Init();
7 }
8};
CComAggObject
,
CComPolyObject
, etc. all define their own implementation
of _AtlInitialConstruct
. At this time, nothing in the
framework overrides _AtlFinalConstruct
or _AtlFinalRelease
. However, _AtlInitialConstruct
is used; when you’re creating
objects, make sure that it gets called or your objects won’t get
initialized properly.
Creators
Because the extra steps to manage the multiphase
construction process are easy to forget, ATL encapsulates this
algorithm into several C++ classes called Creators. Each performs
the appropriate multiphase construction. Each Creator class is
actually just a way to wrap a scope around a single static member
function called CreateInstance
:
1static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv);
The name of the Creator class is used in a type definition associated with the class; this is discussed in the next section.
CComCreator
CComCreator
is a Creator class that
creates either standalone or aggregated instances. It is
parameterized by the C++ class being created; for example, CComObject<CPenguin>
. CComCreator
is
declared like this:
1template <class T1>
2class CComCreator {
3public:
4 static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
5 LPVOID* ppv) {
6 ATLASSERT(ppv != NULL);
7 if (ppv == NULL)
8 return E_POINTER;
9 *ppv = NULL;
10
11 HRESULT hRes = E_OUTOFMEMORY;
12 T1* p = NULL;
13 ATLTRY(p = new T1(pv))
14 if (p != NULL) {
15 p->SetVoid(pv);
16 p->InternalFinalConstructAddRef();
17 hRes = p->_AtlInitialConstruct();
18 if (SUCCEEDED(hRes))
19 hRes = p->FinalConstruct();
20 if (SUCCEEDED(hRes))
21 hRes = p->_AtlFinalConstruct();
22 p->InternalFinalConstructRelease();
23 if (hRes == S_OK)
24 hRes = p->QueryInterface(riid, ppv);
25 if (hRes != S_OK)
26 delete p;
27 }
28 return hRes;
29 }
30};
Using CComCreator
simplifies our class
object implementation quite a bit:
1STDMETHODIMP
2CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
3 void** ppv) {
4 typedef CComCreator<
5 CComPolyObject<CPenguin> > PenguinPolyCreator;
6 return PenguinPolyCreator::CreateInstance(pUnkOuter,
7 riid, ppv);
8}
Notice the use of the type definition to define a new Creator type. If we were to create penguins other places in our server, we would have to rebuild the type definition:
1STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
2 typedef CComCreator< CComObject<CPenguin> > PenguinCreator;
3 return PenguinCreator::CreateInstance(0, IID_IBird, (void**)ppbird);
4}
Defining a Creator like this outside the class
being created has two problems. First, it duplicates the
type-definition code. Second, and more important, we’ve taken away
the right of the CPenguin
class to decide for itself
whether it wants to support aggregation; the type definition is
making this decision now. To reduce code and let the class designer
make the decision about standalone versus aggregate activation, by
convention in ATL, you place the type definition inside the class
declaration and give it the well-known name _CreatorClass
:
1class CPenguin : ... {
2public:
3 ...
4 typedef CComCreator<
5 CComPolyObject<CPenguin> > _CreatorClass;
6};
Using the Creator type definition, creating an
instance and obtaining an initial interface actually involves fewer
lines of code than operator new
and QueryInterface
:
1STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
2 return CPenguin::_CreatorClass::CreateInstance(0,
3 IID_IBird,
4 (void**)ppbird);
5}
Chapter5, “COM Servers,” discusses one other base class that your
class will often derive from, CComCoClass
.
1class CPenguin : ...,
2public CComCoClass<CPenguin, &CLSID_Penguin>, ... {...};
CComCoClass
provides two static member
functions, each called CreateInstance
, that make use of
the class’s creators:
1template <class T, const CLSID* pclsid = &CLSID_NULL>
2class CComCoClass {
3public:
4 ...
5 template <class Q>
6 static HRESULT CreateInstance(IUnknown* punkOuter, Q** pp) {
7 return T::_CreatorClass::CreateInstance(punkOuter,
8 __uuidof(Q), (void**) pp);
9 }
10 template <class Q>
11 static HRESULT CreateInstance(Q** pp) {
12 return T::_CreatorClass::CreateInstance(NULL,
13 __uuidof(Q), (void**) pp);
14 }
15};
This simplifies the creation code still further:
1STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
2 return CPenguin::CreateInstance(ppbird);
3}
CComCreator2
You might like to support
both standalone and aggregate activation using CComObject
and CComAggObject
instead of CComPolyObject
because of the overhead associated with CComPolyObject
in
the standalone case. The decision can be made with a simple if
statement, but then you lose the predefined CreateInstance
code in CComCoClass
. ATL provides CComCreator2
to
make this logic fit within the existing Creator machinery:
1template <class T1, class T2> class CComCreator2 {
2public:
3 static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
4 LPVOID* ppv) {
5 ATLASSERT(*ppv == NULL);
6 return (pv == NULL) ? T1::CreateInstance(NULL, riid, ppv)
7 : T2::CreateInstance(pv, riid, ppv);
8 }
9};
Notice that CComCreator2
is
parameterized by the types of two other Creators. All CComCreator2
does is check for a NULL
pUnkOuter
and forward the call to one of two other Creators. So, if you’d
like to use CComObject
and CComAggObject
instead
of CComPolyObject
, you can do so like this:
1class CPenguin : ... {
2public:
3 ...
4 typedef CComCreator2< CComCreator< CComObject<CPenguin> >,
5 CComCreator< CComAggObject<CPenguin> > >
6 _CreatorClass;
7};
Of course, the beauty of this scheme is that all
the Creators have the same function, CreateInstance
, and
are exposed via a type definition of the same name, _CreatorClass
. Thus, none of the server code that creates
penguins needs to change if the designer of the class changes his
mind about how penguins should be created.
CComFailCreator
One of the changes you
might want to make to your creation scheme is to support either
standalone or aggregate activation only, not both. To make this
happen, you need a special Creator to return an error code to use
in place of one of the Creators passed as template arguments to CComCreator2
. That’s what CComFailCreator
is
for:
1template <HRESULT hr> class CComFailCreator {
2public:
3 static HRESULT WINAPI CreateInstance(void*, REFIID, LPVOID*)
4 { return hr; }
5};
If you’d like standalone activation only, you
can use CComFailCreator
as the aggregation creator
template parameter:
1class CPenguin : ... {
2public:
3 ...
4 typedef CComCreator2< CComCreator< CComObject<CPenguin> >,
5 CComFailCreator<CLASS_E_NOAGGREGATION> >
6 _CreatorClass;
7};
If you’d like aggregate activation only, you can
use CComFailCreator
as the standalone creator
parameter:
1class CPenguin : ... {
2public:
3 ...
4 typedef CComCreator2< CComFailCreator<E_FAIL>,
5 CComCreator< CComAggObject<CPenguin> > >
6 _CreatorClass;
7};
Convenience Macros
As a convenience, ATL provides the following
macros in place of manually specifying the _CreatorClass
type definition for each class:
1#define DECLARE_POLY_AGGREGATABLE(x) public:\
2 typedef ATL::CComCreator< \
3 ATL::CComPolyObject< x > > _CreatorClass;
4
5#define DECLARE_AGGREGATABLE(x) public: \
6 typedef ATL::CComCreator2< \
7 ATL::CComCreator< ATL::CComObject< x > >, \
8 ATL::CComCreator< ATL::CComAggObject< x > > > \
9 _CreatorClass;
10
11#define DECLARE_NOT_AGGREGATABLE(x) public:\
12 typedef ATL::CComCreator2< \
13 ATL::CComCreator< ATL::CComObject< x > >, \
14 ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > \
15 _CreatorClass;
16
17#define DECLARE_ONLY_AGGREGATABLE(x) public:\
18 typedef ATL::CComCreator2< \
19 ATL::CComFailCreator<E_FAIL>, \
20 ATL::CComCreator< ATL::CComAggObject< x > > > \
21 _CreatorClass;
Using these macros, you can
declare that CPenguin
can be activated both standalone and
aggregated like this:
1class CPenguin : ... {
2public:
3 ...
4 DECLARE_AGGREGATABLE(CPenguin)
5};
Table4.3 summarizes the classes the Creators use to derive from your class.
Table 4.3. Creator Type-Definition Macros
Macro |
Standalone |
Aggregation |
---|---|---|
|
|
|
|
|
– |
|
– |
|
|
|
|
Private Initialization
Creators are handy because they follow the
multiphase construction sequence ATL-based objects use. However,
Creators return only an interface pointer, not a pointer to the implementing class (as in IBird*
instead of CPenguin*
). This can be a
problem if the class exposes public member functions or if member
data is not available via a COM interface. Your first instinct as a
former C programmer might be to simply cast the resultant interface
pointer to the type you’d like:
1STDMETHODIMP
2CAviary::CreatePenguin(BSTR bstrName, long nWingspan,
3 IBird** ppbird) {
4 HRESULT hr;
5 hr = CPenguin::_CreatorClass::CreateInstance(0,
6 IID_IBird, (void**)ppbird);
7 if( SUCCEEDED(hr) ) {
8 // Resist this instinct!
9 CPenguin* pPenguin = (CPenguin*)(*ppbird);
10 pPenguin->Init(bstrName, nWingspan);
11 }
12 return hr;
13}
Unfortunately, because QueryInterface
allows interfaces of a single COM identity to be implemented on
multiple C++ objects or even multiple COM objects, in many cases a
cast won’t work. Instead, you should use the CreateInstance
static member functions of CComObject
, CComAggObject
, and CComPolyObject
:
1static HRESULT WINAPI
2CComObject::CreateInstance(CComObject<Base>** pp);
3
4static HRESULT WINAPI
5CComAggObject::CreateInstance(IUnknown* puo,
6 CComAggObject<contained>** pp);
7
8static HRESULT WINAPI
9CComPolyObject::CreateInstance(IUnknown* puo,
10 CComPolyObject<contained>** pp);
These static member functions do not make
Creators out of CComObject
, CComAggObject
, or CComPolyObject
, but they each perform the additional work
required to call the object’s FinalConstruct
(and _AtlInitialConstruct
, and so on) member functions. The
reason to use them, however, is that each of them returns a pointer
to the most derived class:
1STDMETHODIMP
2CAviary::CreatePenguin(BSTR bstrName, long nWingspan,
3 IBird** ppbird) {
4 HRESULT hr;
5 CComObject<CPenguin>* pPenguin = 0;
6 hr = CComObject<CPenguin>::CreateInstance(&pPenguin);
7 if( SUCCEEDED(hr) ) {
8 pPenguin->AddRef();
9 pPenguin->Init(bstrName, nWingspan);
10 hr = pPenguin->QueryInterface(IID_IBird, (void**)ppbird);
11 pPenguin->Release();
12 }
13 return hr;
14}
The class you use for
creation in this manner depends on the kind of activation you want.
For standalone activation, use CComObject::CreateInstance
.
For aggregated activation, use CComAggObject::CreateInstance
. For either standalone or
aggregated activation that saves a set of vtbl
s at the
expense of per-instance overhead, use CComPolyObject::CreateInstance
.
Multiphase Construction on the Stack
When creating an instance of an ATL-based COM
object, you should always use a Creator (or the static CreateInstance
member function of CComObject
, et
al) instead of the C++ operator new
. However, if you’ve
got a global or a static object, or an object that’s allocated on
the stack, you can’t use a Creator because you’re not calling new
. As discussed earlier, ATL provides two classes for
creating instances that aren’t on the heap: CComObjectGlobal
and CComObjectStack
. However,
instead of requiring you to call FinalConstruct
(and FinalRelease
) manually, both of these classes perform the
proper initialization and shutdown in their constructors and
destructors, as shown here in CComObjectGlobal
:
1template <class Base>
2class CComObjectGlobal : public Base {
3public:
4 typedef Base _BaseClass;
5 CComObjectGlobal(void* = NULL) {
6 m_hResFinalConstruct = S_OK;
7 __if_exists(FinalConstruct) {
8 __if_exists(InternalFinalConstructAddRef) {
9 InternalFinalConstructAddRef();
10 }
11 m_hResFinalConstruct = _AtlInitialConstruct();
12 if (SUCCEEDED(m_hResFinalConstruct))
13 m_hResFinalConstruct = FinalConstruct();
14 __if_exists(InternalFinalConstructRelease) {
15 InternalFinalConstructRelease();
16 }
17 }
18 }
19 ~CComObjectGlobal() {
20 __if_exists(FinalRelease) {
21 FinalRelease();
22 }
23 }
24 ...
25 HRESULT m_hResFinalConstruct;
26};
Because there is no return
code from a constructor, if you’re interested in the result from FinalConstruct
, you must check the cached result in the
public member variable m_hResFinalConstruct
.
Note in the previous code the use of the new __if_exists
C++ keyword. This keyword allows for
conditional compilation based on the presence of a symbol or member
function. Derived classes, for instance, can check for the
existence of particular members of a base class. Alternatively, the __if_not_exists
keyword can be used to conditionally
compile code based on the absence of specific symbol. These
keywords are analogous to the #ifdef
and #ifndef
preprocessor directives, except that they operate on symbols that
are not removed during the preprocessing stage.
Debugging
ATL provides a number of helpful debugging
facilities, including both a normal and a categorized wrapper for
producing debug output, a macro for making assertions, and debug
output for tracing calls to QueryInterface
,
AddRef
, and Release
on an interface-by-interface
basis. Of course, during a release build, all these debugging
facilities fall away to produce the smallest, fastest binary image
possible.
Making Assertions
Potentially the best debugging technique is to
use assertions, which enable you to make assumptions in your code
and, if those assumptions are invalidated, to be notified
immediately. Although ATL doesn’t exactly support assertions, it
does provide the ATLASSERT
macro. However, it’s actually
just another name for the Microsoft CRT macro _ASSERTE
:
1#ifndef ATLASSERT
2#define ATLASSERT(expr) _ASSERTE(expr)
3#endif
Flexible Debug Output
OutputDebugString
is handy as the Win32 equivalent of printf
, but it takes
only a single string argument. We want a printf
that
outputs to debug output instead of standard output. ATL provides
the AtlTrace
function to do exactly that:
1inline void _cdecl AtlTrace(LPCSTR pszFormat, ...)
2inline void _cdecl AtlTrace(LPCWSTR pszFormat, ...)
Instead of calling the function directly, use
the macro ATLTRACE
. The macro calls the underlying
function, but also adds file and line number information to the
trace output. The macro expands to either a call to AtlTrace
or nothing, depending on whether the _DEBUG
symbol is defined. Typical usage is as follows:
1HRESULT CPenguin::FinalConstruct() {
2 ATLTRACE(__TEXT("%d+%d= %d\n"), 2, 2, 2+2);
3}
ATLTRACE
always generates output to the
debug window. If you’d like to be even more selective about what
makes it to debug output, ATL provides a second trace function, AtlTrace2
, also with its own macro, ATLTRACE2
:
1void AtlTrace2(DWORD_PTR dwCategory, UINT nLevel,
2 LPCSTR pszFormat, ...)
3void AtlTrace2(DWORD_PTR dwCategory, UINT nLevel,
4 LPCWSTR pszFormat, ...)
In addition to the format string and the
variable arguments, AtlTrace2
takes a trace category and a
trace level. The trace category is defined as an instance of the CTraceCategory
class. ATL includes the following trace
categories, already defined:
1#ifdef _DEBUG
2#define DECLARE_TRACE_CATEGORY( name ) \
3 extern ATL::CTraceCategory name;
4#else
5#define DECLARE_TRACE_CATEGORY( name ) const DWORD_PTR name = 0;
6#endif
7
8DECLARE_TRACE_CATEGORY( atlTraceGeneral )
9DECLARE_TRACE_CATEGORY( atlTraceCOM )
10DECLARE_TRACE_CATEGORY( atlTraceQI )
11DECLARE_TRACE_CATEGORY( atlTraceRegistrar )
12DECLARE_TRACE_CATEGORY( atlTraceRefcount )
13DECLARE_TRACE_CATEGORY( atlTraceWindowing )
14DECLARE_TRACE_CATEGORY( atlTraceControls )
15DECLARE_TRACE_CATEGORY( atlTraceHosting )
16DECLARE_TRACE_CATEGORY( atlTraceDBClient )
17DECLARE_TRACE_CATEGORY( atlTraceDBProvider )
18DECLARE_TRACE_CATEGORY( atlTraceSnapin )
19DECLARE_TRACE_CATEGORY( atlTraceNotImpl )
20DECLARE_TRACE_CATEGORY( atlTraceAllocation )
21DECLARE_TRACE_CATEGORY( atlTraceException )
22DECLARE_TRACE_CATEGORY( atlTraceTime )
23DECLARE_TRACE_CATEGORY( atlTraceCache )
24DECLARE_TRACE_CATEGORY( atlTraceStencil )
25DECLARE_TRACE_CATEGORY( atlTraceString )
26DECLARE_TRACE_CATEGORY( atlTraceMap )
27DECLARE_TRACE_CATEGORY( atlTraceUtil )
28DECLARE_TRACE_CATEGORY( atlTraceSecurity )
29DECLARE_TRACE_CATEGORY( atlTraceSync )
30DECLARE_TRACE_CATEGORY( atlTraceISAPI )
31
32// atlTraceUser categories are no longer needed.
33// Just declare your own trace category using CTraceCategory.
34DECLARE_TRACE_CATEGORY( atlTraceUser )
35DECLARE_TRACE_CATEGORY( atlTraceUser2 )
36DECLARE_TRACE_CATEGORY( atlTraceUser3 )
37DECLARE_TRACE_CATEGORY( atlTraceUser4 )
38
39#pragma deprecated( atlTraceUser )
40#pragma deprecated( atlTraceUser2 )
41#pragma deprecated( atlTraceUser3 )
42#pragma deprecated( atlTraceUser4 )
The CTraceCategory
class associates the
category name with the underlying value so that it appears in the
trace listing. The four atlTraceUserX
categories exist for
backward-compatibility; ATL versions 7 and earlier had no means of
defining custom trace categories. For new code, you simply need to
create a global instance of CTraceCategory
like this:
1CTraceCategory PenguinTraces( "CPenguin trace", 1 );
2...
3STDMETHODIMP CPenguin::Fly() {
4 ATLTRACE2(PenguinTraces, 2,
5 _T("IBird::Fly\n"));
6 ATLTRACE2(PenguinTraces, 42,
7 _T("Hmmm... Penguins can't fly...\n"));
8 ATLTRACE2(atlTraceNotImpl, 0,
9 _T("IBird::Fly not implemented!\n"));
10 return E_NOTIMPL;
11}
The trace level is a measure of severity, with 0 the most severe. ATL itself uses only levels 0 and 2. The documentation recommends that you stay between 0 and 4, but you can use any level up to 4,294,967,295 (although that might be a little too fine grained to be useful).
Also, because ATL uses atlTraceNotImpl
so often, there’s even a special macro for it:
1#define ATLTRACENOTIMPL(funcname) \
2 ATLTRACE2(atlTraceNotImpl, 2, \
3 _T("ATL: %s not implemented.\n"), funcname); \
4 return E_NOTIMPL
This macro is used a lot in the implementations of the OLE interfaces:
1STDMETHOD(SetMoniker)(DWORD, IMoniker*) {
2 ATLTRACENOTIMPL(_T("IOleObjectImpl::SetMoniker"));
3}
Tracing Calls to QueryInterface
ATL’s implementation of QueryInterface
is especially well instrumented for debugging. If you define the _ATL_DEBUG_QI
symbol before compiling, your objects will
output their class name, the interface being queried for (by
name [10] , if available), and whether the query
succeeded or failed. This is extremely useful for reverse
engineering clients’ interface requirements. For example, here’s a
sample of the _ATL_DEBUG_QI
output when hosting a control
in IE6:
Interface names for remotable interfaces are available in the
Registry as the default value of the HKEY_CLASSES_ROOT\{IID}
key.
CComClassFactory - IUnknown
CComClassFactory - IClassFactory
CComClassFactory - IClassFactory
CComClassFactory - - failed
CPenguin - IUnknown
CPenguin - - failed
CPenguin - IOleControl
CPenguin - IClientSecurity - failed
CPenguin - IQuickActivate
CPenguin - IOleObject
CPenguin - IViewObjectEx
CPenguin - IPointerInactive - failed
CPenguin - IProvideClassInfo2
CPenguin - IConnectionPointContainer - failed
CPenguin - IPersistPropertyBag2 - failed
CPenguin - IPersistPropertyBag - failed
CPenguin - IPersistStreamInit
CPenguin - IViewObjectEx
CPenguin - IActiveScript - failed
CPenguin - - failed
CPenguin - IOleControl
CPenguin - IOleCommandTarget - failed
CPenguin - IDispatchEx - failed
CPenguin - IDispatch
CPenguin - IOleControl
CPenguin - IOleObject
CPenguin - IOleObject
CPenguin - IRunnableObject - failed
CPenguin - IOleObject
CPenguin - IOleInPlaceObject
CPenguin - IOleInPlaceObjectWindowless
CPenguin - IOleInPlaceActiveObject
CPenguin - IOleControl
CPenguin - IClientSecurity - failed
Tracing Calls to AddRef and Release
The only calls more heavily
instrumented for debugging than QueryInterface
are AddRef
and Release
. ATL provides an elaborate
scheme for tracking calls to AddRef
and Release
on individual interfaces. It is elaborate because each ATL-based
C++ class has a single implementation of AddRef
and Release
, implemented in the most derived class; for example, CComObject
. To overcome this limitation, when _ATL_DEBUG_INTERFACES
is defined, ATL wraps each new
interface [11] handed out via QueryInterface
in another C++ object that implements a single interface. Each of
these “thunk objects” keeps track of the real interface pointer, as
well as the name of the interface and the name of the class that
has implemented the interface. The thunk objects also keep track of
an interface pointer specific reference count that is managed, along
with the object’s reference count, in the thunk object’s
implementation of AddRef
and Release
. As calls to AddRef
and Release
are made, each thunk object
knows exactly which interface is being used and dumps reference
count information to debug output. For example, here’s the same
interaction between a control and IE6, but using _ATL_DEBUG_INTERFACES
instead of _ATL_DEBUG_QI
:
ATL
makes sure to always hand out the same thunk for each object’s IUnknown*
to observe the rules of COM identity as
discussed in Chapter5, “COM Servers.”
QIThunk-1 AddRef: Object=0x021c2c88 Refcount=1 CComClassFactory-IUnknown
IThunk-2 AddRef: Object=0x021c2c88 Refcount=1 CComClassFactory-IClassFactory
QIThunk-2 AddRef: Object=0x021c2c88 Refcount=2 CComClassFactory-IClassFactory
QIThunk-2 Release: Object=0x021c2c88 Refcount=1 CComClassFactory-IClassFactory
QIThunk-3 AddRef: Object=0x021c2c88 Refcount=1 CComClassFactory-IClassFactory
QIThunk-2 Release: Object=0x021c2c88 Refcount=0 CComClassFactory-IClassFactory
QIThunk-4 AddRef: Object=0x021c2e38 Refcount=1 CPenguin-IUnknown
QIThunk-5 AddRef: Object=0x021c2e40 Refcount=1 CPenguin-IOleControl
QIThunk-5 Release: Object=0x021c2e40 Refcount=0 CPenguin-IOleControl
QIThunk-6 AddRef: Object=0x021c2e60 Refcount=1 CPenguin-IQuickActivate
QIThunk-7 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-8 AddRef: Object=0x021c2e4c Refcount=1 CPenguin-IViewObjectEx
QIThunk-9 AddRef: Object=0x021c2e68 Refcount=1 CPenguin-IProvideClassInfo2
QIThunk-9 Release: Object=0x021c2e68 Refcount=0 CPenguin-IProvideClassInfo2
QIThunk-8 Release: Object=0x021c2e4c Refcount=0 CPenguin-IViewObjectEx
QIThunk-7 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-6 Release: Object=0x021c2e60 Refcount=0 CPenguin-IQuickActivate
QIThunk-10 AddRef: Object=0x021c2e3c Refcount=1 CPenguin-IPersistStreamInit
QIThunk-10 Release: Object=0x021c2e3c Refcount=0 CPenguin-IPersistStreamInit
QIThunk-11 AddRef: Object=0x021c2e4c Refcount=1 CPenguin-IViewObjectEx
QIThunk-12 AddRef: Object=0x021c2e40 Refcount=1 CPenguin-IOleControl
QIThunk-12 Release: Object=0x021c2e40 Refcount=0 CPenguin-IOleControl
QIThunk-13 AddRef: Object=0x021c2e38 Refcount=1 CPenguin-IDispatch
QIThunk-14 AddRef: Object=0x021c2e40 Refcount=1 CPenguin-IOleControl
QIThunk-14 Release: Object=0x021c2e40 Refcount=0 CPenguin-IOleControl
QIThunk-3 Release: Object=0x021c2c88 Refcount=0 CComClassFactory-IClassFactory
QIThunk-15 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-16 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-16 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-15 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-17 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-18 AddRef: Object=0x021c2e50 Refcount=1 CPenguin-IOleInPlaceObject
QIThunk-19 AddRef: Object=0x021c2e50 Refcount=1 CPenguin-IOleInPlaceObjectWindowless
QIThunk-20 AddRef: Object=0x021c2e48 Refcount=1 CPenguin-IOleInPlaceActiveObject
QIThunk-20 Release: Object=0x021c2e48 Refcount=0 CPenguin-IOleInPlaceActiveObject
QIThunk-18 Release: Object=0x021c2e50 Refcount=0 CPenguin-IOleInPlaceObject
QIThunk-19 AddRef: Object=0x021c2e50 Refcount=2 CPenguin-IOleInPlaceObjectWindowless
QIThunk-17 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-19 Release: Object=0x021c2e50 Refcount=1 CPenguin-IOleInPlaceObjectWindowless
QIThunk-21 AddRef: Object=0x021c2e40 Refcount=1 CPenguin-IOleControl
QIThunk-21 Release: Object=0x021c2e40 Refcount=0 CPenguin-IOleControl
QIThunk-22 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-23 AddRef: Object=0x021c2e50 Refcount=1 CPenguin-IOleInPlaceObject
QIThunk-19 Release: Object=0x021c2e50 Refcount=0 CPenguin-IOleInPlaceObjectWindowless
QIThunk-23 Release: Object=0x021c2e50 Refcount=0 CPenguin-IOleInPlaceObject
QIThunk-22 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-24 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-25 AddRef: Object=0x021c2e50 Refcount=1 CPenguin-IOleInPlaceObject
QIThunk-25 Release: Object=0x021c2e50 Refcount=0 CPenguin-IOleInPlaceObject
QIThunk-24 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-13 Release: Object=0x021c2e38 Refcount=0 CPenguin-IDispatch
QIThunk-11 Release: Object=0x021c2e4c Refcount=0 CPenguin-IViewObjectEx
QIThunk-26 AddRef: Object=0x021c2e44 Refcount=1 CPenguin-IOleObject
QIThunk-26 Release: Object=0x021c2e44 Refcount=0 CPenguin-IOleObject
QIThunk-4 Release: Object=0x021c2e38 Refcount=0 CPenguin-IUnknown
QIThunk-1 Release: Object=0x021c2c88 Refcount=0 CComClassFactory-IUnknown
ATL maintains a list of
outstanding thunk objects. This list is used at server shutdown to
detect any leaks; that is, any interfaces that the client has not
released. When using _ATL_DEBUG_INTERFACES
, watch your
debug output for the string LEAK
, which is an indication
that someone has mismanaged an interface reference:
ATL: QIThunk - 4 LEAK: Object = 0x00962920 Refcount = 4
MaxRefCount = 4 CCalc - ICalc
The most useful part of this notification is the
index of QI thunk object. You can use this to track when the leaked
interface is acquired by using the CAtlDebugInterfacesModule
class. This is the class that
manages the thunk objects during debug builds, and a global
instance of this class called _AtlDebugInterfacesModule
is
automatically included in your class when the _ATL_DEBUG_INTERFACES
symbol is defined. You can instruct
the debugger to break at the appropriate time by setting the m_nIndexBreakAt
member of the CAtlDebugInterfacesModule
at server start-up time.
1extern "C"
2BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,
3 LPVOID lpReserved) {
4 hInstance;
5 BOOL b = _AtlModule.DllMain(dwReason, lpReserved);
6 // Trace down interface leaks
7#ifdef _ATL_DEBUG_INTERFACES
8 _AtlDebugInterfacesModule.m_nIndexBreakAt = 4;
9#endif
10 return b;
11}
When that interface thunk is
allocated, _AtlDebugInterfacesModule
calls DebugBreak
, handing control over to the debugger and
allowing you to examine the call stack and plug the leak.
_ATL_DEBUG_REFCOUNT
Versions of ATL earlier than version 3 used the _ATL_DEBUG_REFCOUNT
symbol to track interface reference
counts for ATL IXxxImpl
classes only. Because _ATL_DEBUG_INTERFACES
is much more general, it has
replaced _ATL_DEBUG_REFCOUNT
, although _ATL_DEBUG_REFCOUNT
is still supported for backward
compatibility.
1#ifdef _ATL_DEBUG_REFCOUNT
2#ifndef _ATL_DEBUG_INTERFACES
3#define _ATL_DEBUG_INTERFACES
4#endif
5#endif
Summary
ATL provides a layered approach to implementing IUnknown
. The top layer, represented by the CComXxxThreadModel
classes, provides helper functions and
type definitions for synchronization required of both STAs and
MTAs. The second level, CComObjectRootEx
, uses the
threading model classes to support “just thread-safe enough”AddRef
and Release
implementations and
object-level locking. CComObjectRootEx
also provides a
table-driven implementation of QueryInterface
, using an
interface map provided by your class. Your class derives from CComObjectRootEx
and any number of interfaces, providing
the interface member function implementations. The final level is
provided by CComObject
and friends, which provide the
implementation of QueryInterface
, AddRef
, and Release
based on the lifetime and identity requirements of
the object.
To allow each class to define its one lifetime
and identity requirements, each class defines its own _CreatorClass
, which defines the appropriate Creator. The
Creator is responsible for properly creating an instance of your
ATL-base class and should be used in place of the C++ operator new
.
Finally, to debug your objects, ATL provides a number of debugging facilities, including tracing and interface usage and leak tracking.