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
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:
#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
vptr
s are adjusted to the correct values.
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:
void 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:
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
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
:
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.