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:
// CPenguin constructor
CPenguin::CPenguin() {
HRESULT hr = CoCreateInstance(CLSID_EarthAtmosphere, 0,
CLSCTX_ALL, IID_IAir, (void**)&m_pAir);
if( FAILED(hr) ) {
// Can't return an error from a ctor
return hr;
// Can't throw an error without the CRT
throw hr;
// This won't help
OutputDebugString(__T("Help! Can't bre...\n"));
}
}
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:
HRESULT 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:
HRESULT CPenguin::FinalConstruct() {
return CoCreateInstance(CLSID_EarthAtmosphere, 0, CLSCTX_ALL,
IID_IAir, (void**)&m_pAir);
}
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
if( !pUnkOuter ) {
CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
if( !pobj ) return E_OUTOFMEMORY;
HRESULT hr = pobj->FinalConstruct();
if( SUCCEEDED(hr) ) ...
return hr;
}
...
}
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:
// CPenguin implementation
HRESULT CPenguin::FinalConstruct() {
HRESULT hr;
hr = CoCreateInstance(CLSID_EarthAtmosphere, 0, CLSCTX_ALL,
IID_IAir, (void**)&m_pAir);
if( SUCCEEDED(hr) ) {
// Pass reference to object with reference count of 0
hr = m_pAir->CheckSuitability(GetUnknown());
}
return hr;
}
// CEarthAtmosphere implementation in separate server
STDMETHODIMP CEarthAtmosphere::CheckSuitability(IUnknown* punk) {
IBreatheO2* pbo2 = 0;
HRESULT hr = E_FAIL;
// CPenguin's lifetime increased to 1 via QI
hr = punk->QueryInterface(IID_IBreatheO2, (void**)&pbo2);
if( SUCCEEDED(hr) ) {
pbo2->Release(); // During this call, lifetime decreases
// to 0 and destruction sequence begins...
}
return (SUCCEEDED(hr) ? S_OK : E_FAIL);
}
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:
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
if( !pUnkOuter ) {
CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
if( FAILED(hr) ) return E_OUTOFMEMORY;
// Protect object from pre-mature destruction
pobj->InternalAddRef();
hr = pobj->FinalConstruct();
pobj->InternalRelease();
if( SUCCEEDED(hr) ) ...
return hr;
}
...
}
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:
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
if( !pUnkOuter ) {
CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
if( FAILED(hr) ) return E_OUTOFMEMORY;
// Protect object from pre-mature destruction (maybe)
pobj->InternalFinalConstructAddRef();
hr = pobj->FinalConstruct();
pobj->InternalFinalConstructRelease();
if( SUCCEEDED(hr) ) ...
return hr;
}
...
}
By
default, InternalFinalConstructAddRef and
InternalFinalConstructRelease incur no release build
runtime overhead:
class CComObjectRootBase {
public:
...
void InternalFinalConstructAddRef() {}
void InternalFinalConstructRelease() {
ATLASSERT(m_dwRef == 0);
}
...
};
To change the implementation of
InternalFinalConstructAddRef and
InternalFinalConstructRelease to provide reference count
safety, ATL provides the following macro:
#define DECLARE_PROTECT_FINAL_CONSTRUCT() \
void InternalFinalConstructAddRef() { InternalAddRef(); } \
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:
class CPenguin : ... {
public:
HRESULT FinalConstruct();
DECLARE_PROTECT_FINAL_CONSTRUCT()
...
};
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.
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:
class Base {
public:
Base() { Init(); }
virtual void Init() {}
};
class Derived : public Base {
public:
virtual void Init() {}
};
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 vtbls, 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 vptrs during construction. If the base
class is an abstract base class, this often results in
vtbls 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:
#ifdef _ATL_DISABLE_NO_VTABLE
#define ATL_NO_VTABLE
#else
#define ATL_NO_VTABLE __declspec(novtable)
#endif
Unless the _ATL_DISABLE_NO_VTABLE is
defined, a class defined using _ATL_NO_VTABLE has its
constructor behavior adjusted with
__declspec(novtable):
class 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. 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 vptrs are adjusted to
the correct values.
One last thing should be mentioned about
__declspec(novatble). Just as it turns off the adjustment
of vptrs during construction, it turns off the adjustment
of vptrs 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:
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:
CComObject::~CComObject() {
m_dwRef = -(LONG_MAX/2);
FinalRelease();
_AtlModule->Unlock();
}
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:
class CComObjectRootBase {
public:
...
// For library initialization only
HRESULT _AtlFinalConstruct() {
return S_OK;
}
...
void _AtlFinalRelease() {} // temp
};
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:
template <class ThreadModel>
class CComObjectRootEx : public CComObjectRootBase {
public:
...
HRESULT _AtlInitialConstruct() {
return m_critsec.Init();
}
};
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:
static 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 createdfor example,
CComObject<CPenguin>. CComCreator is
declared like this:
template <class T1>
class CComCreator {
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
LPVOID* ppv) {
ATLASSERT(ppv != NULL);
if (ppv == NULL)
return E_POINTER;
*ppv = NULL;
HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;
ATLTRY(p = new T1(pv))
if (p != NULL) {
p->SetVoid(pv);
p->InternalFinalConstructAddRef();
hRes = p->_AtlInitialConstruct();
if (SUCCEEDED(hRes))
hRes = p->FinalConstruct();
if (SUCCEEDED(hRes))
hRes = p->_AtlFinalConstruct();
p->InternalFinalConstructRelease();
if (hRes == S_OK)
hRes = p->QueryInterface(riid, ppv);
if (hRes != S_OK)
delete p;
}
return hRes;
}
};
Using CComCreator simplifies our class
object implementation quite a bit:
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
typedef CComCreator<
CComPolyObject<CPenguin> > PenguinPolyCreator;
return PenguinPolyCreator::CreateInstance(pUnkOuter,
riid, ppv);
}
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:
STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
typedef CComCreator< CComObject<CPenguin> > PenguinCreator;
return PenguinCreator::CreateInstance(0, IID_IBird, (void**)ppbird);
}
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:
class CPenguin : ... {
public:
...
typedef CComCreator<
CComPolyObject<CPenguin> > _CreatorClass;
};
Using the Creator type definition, creating an
instance and obtaining an initial interface actually involves fewer
lines of code than operator new and
QueryInterface:
STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
return CPenguin::_CreatorClass::CreateInstance(0,
IID_IBird,
(void**)ppbird);
}
Chapter
5, "COM Servers," discusses one other base class that your
class will often derive from, CComCoClass.
class CPenguin : ...,
public CComCoClass<CPenguin, &CLSID_Penguin>, ... {...};
CComCoClass provides two static member
functions, each called CreateInstance, that make use of
the class's creators:
template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass {
public:
...
template <class Q>
static HRESULT CreateInstance(IUnknown* punkOuter, Q** pp) {
return T::_CreatorClass::CreateInstance(punkOuter,
__uuidof(Q), (void**) pp);
}
template <class Q>
static HRESULT CreateInstance(Q** pp) {
return T::_CreatorClass::CreateInstance(NULL,
__uuidof(Q), (void**) pp);
}
};
This simplifies the creation code still
further:
STDMETHODIMP CAviary::CreatePenguin(IBird** ppbird) {
return CPenguin::CreateInstance(ppbird);
}
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:
template <class T1, class T2> class CComCreator2 {
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
LPVOID* ppv) {
ATLASSERT(*ppv == NULL);
return (pv == NULL) ? T1::CreateInstance(NULL, riid, ppv)
: T2::CreateInstance(pv, riid, ppv);
}
};
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:
class CPenguin : ... {
public:
...
typedef CComCreator2< CComCreator< CComObject<CPenguin> >,
CComCreator< CComAggObject<CPenguin> > >
_CreatorClass;
};
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:
template <HRESULT hr> class CComFailCreator {
public:
static HRESULT WINAPI CreateInstance(void*, REFIID, LPVOID*)
{ return hr; }
};
If you'd like standalone activation only, you
can use CComFailCreator as the aggregation creator
template parameter:
class CPenguin : ... {
public:
...
typedef CComCreator2< CComCreator< CComObject<CPenguin> >,
CComFailCreator<CLASS_E_NOAGGREGATION> >
_CreatorClass;
};
If you'd like aggregate activation only, you can
use CComFailCreator as the standalone creator
parameter:
class CPenguin : ... {
public:
...
typedef CComCreator2< CComFailCreator<E_FAIL>,
CComCreator< CComAggObject<CPenguin> > >
_CreatorClass;
};
Convenience
Macros
As a convenience, ATL provides the following
macros in place of manually specifying the _CreatorClass
type definition for each class:
#define DECLARE_POLY_AGGREGATABLE(x) public:\
typedef ATL::CComCreator< \
ATL::CComPolyObject< x > > _CreatorClass;
#define DECLARE_AGGREGATABLE(x) public: \
typedef ATL::CComCreator2< \
ATL::CComCreator< ATL::CComObject< x > >, \
ATL::CComCreator< ATL::CComAggObject< x > > > \
_CreatorClass;
#define DECLARE_NOT_AGGREGATABLE(x) public:\
typedef ATL::CComCreator2< \
ATL::CComCreator< ATL::CComObject< x > >, \
ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > \
_CreatorClass;
#define DECLARE_ONLY_AGGREGATABLE(x) public:\
typedef ATL::CComCreator2< \
ATL::CComFailCreator<E_FAIL>, \
ATL::CComCreator< ATL::CComAggObject< x > > > \
_CreatorClass;
Using these macros, you can
declare that CPenguin can be activated both standalone and
aggregated like this:
class CPenguin : ... {
public:
...
DECLARE_AGGREGATABLE(CPenguin)
};
Table
4.3 summarizes the classes the Creators use to derive from your
class.
Table 4.3. Creator Type-Definition
Macros
Macro
|
Standalone
|
Aggregation
|
DECLARE_AGGREGATABLE
|
CComObject
|
CComAggObject
|
DECLARE_NOT_AGGREGATABLE
|
CComObject
|
|
DECLARE_ONLY_AGGREGATABLE
|
|
CComAggObject
|
DECLARE_POLY_AGGREGATABLE
|
CComPolyObject
|
CComPolyObject
|
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:
STDMETHODIMP
CAviary::CreatePenguin(BSTR bstrName, long nWingspan,
IBird** ppbird) {
HRESULT hr;
hr = CPenguin::_CreatorClass::CreateInstance(0,
IID_IBird, (void**)ppbird);
if( SUCCEEDED(hr) ) {
// Resist this instinct!
CPenguin* pPenguin = (CPenguin*)(*ppbird);
pPenguin->Init(bstrName, nWingspan);
}
return hr;
}
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:
static HRESULT WINAPI
CComObject::CreateInstance(CComObject<Base>** pp);
static HRESULT WINAPI
CComAggObject::CreateInstance(IUnknown* puo,
CComAggObject<contained>** pp);
static HRESULT WINAPI
CComPolyObject::CreateInstance(IUnknown* puo,
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:
STDMETHODIMP
CAviary::CreatePenguin(BSTR bstrName, long nWingspan,
IBird** ppbird) {
HRESULT hr;
CComObject<CPenguin>* pPenguin = 0;
hr = CComObject<CPenguin>::CreateInstance(&pPenguin);
if( SUCCEEDED(hr) ) {
pPenguin->AddRef();
pPenguin->Init(bstrName, nWingspan);
hr = pPenguin->QueryInterface(IID_IBird, (void**)ppbird);
pPenguin->Release();
}
return hr;
}
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 vtbls 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:
template <class Base>
class CComObjectGlobal : public Base {
public:
typedef Base _BaseClass;
CComObjectGlobal(void* = NULL) {
m_hResFinalConstruct = S_OK;
__if_exists(FinalConstruct) {
__if_exists(InternalFinalConstructAddRef) {
InternalFinalConstructAddRef();
}
m_hResFinalConstruct = _AtlInitialConstruct();
if (SUCCEEDED(m_hResFinalConstruct))
m_hResFinalConstruct = FinalConstruct();
__if_exists(InternalFinalConstructRelease) {
InternalFinalConstructRelease();
}
}
}
~CComObjectGlobal() {
__if_exists(FinalRelease) {
FinalRelease();
}
}
...
HRESULT m_hResFinalConstruct;
};
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.
|