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.[8]

[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:

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.[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 vptrs are adjusted to the correct values.

[9] 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 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:

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




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net