previous page
next page

CComObject Et Al

Consider the following C++ class:

class CPenguin :
  public CComObjectRootEx<CComMultiThreadModel>,
  public IBird,
  public ISnappyDresser {
public:
BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
END_COM_MAP()
    // IBird and ISnappyDresser methods...
    // IUnknown methods not implemented here
};

Because this class doesn't implement the methods of IUnknown, the following will fail at compile time:

STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter,
    REFIID riid, void** ppv) {
    ...
    CPenguin* pobj = new CPenguin; // IUnknown not implemented
    ...
}

Given CComObjectRootBase, you can easily implement the methods of IUnknown:

// Server lifetime management
extern void ServerLock();
extern void ServerUnlock();

class CPenguin :
  public CComObjectRootEx<CComMultiThreadModel>,
  public IBird,
  public ISnappyDresser {
public:
    CPengin() { ServerLock(); }
    ~CPenguin() { ServerUnlock(); }
BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
END_COM_MAP()
    // IBird and ISnappyDresser methods...
    // IUnknown methods for standalone, heap-based objects
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
    { return _InternalQueryInterface(riid, ppv); }

    STDMETHODIMP_(ULONG) AddRef()
    { return InternalAddRef(); }

    STDMETHODIMP_(ULONG) Release() {
        ULONG l = InternalRelease();
        if( l == 0 ) delete this;
        return l;
    }
};

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:

template <class Base>                                            
class CComObject : public Base {                                 
public:                                                          
    typedef Base _BaseClass;                                     
    CComObject(void* = NULL)                                     
    { _pAtlModule->Lock(); }    // Keeps server loaded           
                                                                 
    // Set refcount to -(LONG_MAX/2) to protect destruction and  
    // also catch mismatched Release in debug builds             
    ~CComObject() {                                              
        m_dwRef = -(LONG_MAX/2);                                 
        FinalRelease();                                          
#ifdef _ATL_DEBUG_INTERFACES                                     
        _AtlDebugInterfacesModule.DeleteNonAddRefThunk(          
            _GetRawUnknown());                                   
#endif                                                           
        _pAtlModule->Unlock();   // Allows server to unload      
    }                                                            
    STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}       
    STDMETHOD_(ULONG, Release)() {                               
        ULONG l = InternalRelease();                             
        if (l == 0) delete this;                                 
        return l;                                                
    }                                                            
                                                                 
    STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)     
    {return _InternalQueryInterface(iid, ppvObject);}            
                                                                 
    template <class Q>                                           
    HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)             
    { return QueryInterface(__uuidof(Q), (void**)pp); }          
                                                                 
    static HRESULT WINAPI CreateInstance(CComObject<Base>** pp) ;
};                                                               

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

STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
    void** ppv) {
    *ppv = 0;
    if( pUnkOuter ) return CLASS_E_NOAGGREGATION;
    // Read on for why not to use new like this!
    CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
    if( pobj ) {
        pobj->AddRef();
        HRESULT hr = pobj->QueryInterface(riid, ppv);
        pobj->Release();
        return hr;
    }
    return E_OUTOFMEMORY;
}

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

[7] For a brief description of member function templates, see Appendix A, "C++ Templates by Example."

template <class Q>                                 
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)   
{ 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 (declspecs):

struct __declspec(uuid("00000000-0000-0000-C000-000000000046") IUnknown
{...};

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:

void TryToFly(IUnknown* punk) {
    IBird* pbird = 0;
    if( SUCCEEDED(punk->QueryInterface(__uuidof(pbird),
        (void**)&pbird) ) {
        pbird->Fly();
        pbird->Release();
    }
}

Using the QueryInterface member function template provided in CComObject offers a bit more syntactic convenience, given a CComObject-based object reference:

void TryToFly(CComObject<CPenguin>* pPenguin) {
    IBird* pbird = 0;
    if( SUCCEEDED(pPenguin->QueryInterface(&pbird) ) {
      pbird->Fly();
      pbird->Release();
    }
}

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 asor instead ofstandalone 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 innerthat 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 CComContained-Object, as shown here:

template <class contained>                                    
class CComAggObject :                                         
    public IUnknown,                                          
    public CComObjectRootEx<                                  
        contained::_ThreadModel::ThreadModelNoCS> {           
public:                                                       
    typedef contained _BaseClass;                             
    CComAggObject(void* pv) : m_contained(pv)                 
    { _pAtlModule->Lock(); }                                  
                                                              
    ~CComAggObject() {                                        
        m_dwRef = -(LONG_MAX/2);                              
        FinalRelease();                                       
        _pAtlModule->Unlock();                                
    }                                                         
                                                              
    STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
        ATLASSERT(ppvObject != NULL);                         
        if (ppvObject == NULL)                                
            return E_POINTER;                                 
        *ppvObject = NULL;                                    
                                                              
        HRESULT hRes = S_OK;                                  
        if (InlineIsEqualUnknown(iid)) {                      
            *ppvObject = (void*)(IUnknown*)this;              
            AddRef();                                         
        }                                                     
        else                                                  
            hRes = m_contained._InternalQueryInterface(iid,   
                ppvObject);                                   
        return hRes;                                          
    }                                                         
                                                              
    STDMETHOD_(ULONG, AddRef)()                               
    { return InternalAddRef(); }                              
                                                              
    STDMETHOD_(ULONG, Release)() {                            
        ULONG l = InternalRelease();                          
        if (l == 0) delete this;                              
        return l;                                             
    }                                                         
                                                              
    template <class Q>                                        
    HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)          
    { return QueryInterface(__uuidof(Q), (void**)pp); }       
    static HRESULT WINAPI CreateInstance(LPUNKNOWN pUnkOuter, 
        CComAggObject<contained>** pp);                       
                                                              
    CComContainedObject<contained> m_contained;               
};                                                            

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_p-OuterUnknown 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:

template <class Base>                                         
class CComContainedObject : public Base {                     
public:                                                       
    typedef Base _BaseClass;                                  
    CComContainedObject(void* pv) {                           
        m_pOuterUnknown = (IUnknown*)pv;                      
    }                                                         
                                                              
    STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
      return OuterQueryInterface(iid, ppvObject);             
    }                                                         
                                                              
    STDMETHOD_(ULONG, AddRef)()                               
    { return OuterAddRef(); }                                 
                                                              
    STDMETHOD_(ULONG, Release)()                              
    { return OuterRelease(); }                                
                                                              
    template <class Q>                                        
    HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)          
    { return QueryInterface(__uuidof(Q), (void**)pp); }       
                                                              
    IUnknown* GetControllingUnknown()                         
    { return m_pOuterUnknown; }                               
};                                                            

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:

STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
    void** ppv) {
    *ppv = 0;
    if( pUnkOuter ) {
        CComAggObject<CPenguin>* pobj =
                       new CComAggObject<CPenguin>(pUnkOuter);
        ...
    }
    else {
        CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
        ...
    }
}

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 vtbls. By using both CComAggObject<CPenguin> and CComObject<CPenguin>, we've created two classes and, therefore, two sets of vtbls. 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 vtbls. You do this by using CComPolyObject in place of both CComObject and CComAggObject:

STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
    void** ppv) {
    *ppv = 0;
    CComPolyObject<CPenguin>* pobj =
        new CComPolyObject<CPenguin>(pUnkOuter);
    ...
}

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:

class CComPolyObject :                                          
    public IUnknown,                                            
    public CComObjectRootEx<                                    
        contained::_ThreadModel::ThreadModelNoCS> {             
public:                                                         
    ...                                                         
    CComPolyObject(void* pv) : m_contained(pv ? pv : this) {...}
    ...                                                         
};                                                              

The use of CComPolyObject saves a set of vtbls, 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:

template <class Base>                 
class CComObjectCached : public Base {
public:                               
    ...                               
    STDMETHOD_(ULONG, AddRef)() {     
        ULONG l = InternalAddRef();   
        if (l == 2)                   
            _pAtlModule->Lock();      
        return l;                     
    }                                 
    STDMETHOD_(ULONG, Release)() {    
        ULONG l = InternalRelease();  
        if (l == 0)                   
            delete this;              
        else if (l == 1)              
            _pAtlModule->Unlock();    
        return l;                     
    }                                 
    ...                               
};                                    

Cached objects are useful for in-process class objects:

static CComObjectCached<CPenguinCO>* g_pPenguinCO = 0;

BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, void*) {
  switch( dwReason ) {
  case DLL_PROCESS_ATTACH:
      g_pPenguinCO = new CComObjectCached<CPenguinCO>();

      // 1st ref. doesn't keep server alive
      if( g_pPenguinCO ) g_pPenguinCO->AddRef();
  break;

  case DLL_PROCESS_DETACH:
      if( g_pPenguinCO ) g_pPenguinCO->Release();
  break;
  }
  return TRUE;
}

STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
    void** ppv) {
    // Subsequent references do keep server alive
    if( clsid == CLSID_Penguin && g_pPenguinCO )
      return g_pPenguinCO->QueryInterface(riid, ppv);
    return CLASS_E_CLASSNOTAVAILABLE;
}

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:

template <class Base>                 
class CComObjectNoLock : public Base {
public:                               
    ...                               
    STDMETHOD_(ULONG, AddRef)()       
    { return InternalAddRef(); }      
                                      
    STDMETHOD_(ULONG, Release)() {    
        ULONG l = InternalRelease();  
        if (l == 0) delete this;      
        return l;                     
    }                                 
  ...                                 
};                                    

No-lock objects are useful for out-of-process class objects:

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
    CoInitialize(0);

    CComObjectNoLock<CPenguinCO>* pPenguinCO =
        new CComObjectNoLock<CPenguinCO>();
    if( !pPenguinCO ) return E_OUTOFMEMORY;
        pPenguinCO->AddRef();

    DWORD   dwReg;
    HRESULT hr;

    // Reference(s) cached by ole32.dll won't keep server
    // from shutting down
    hr = CoRegisterClassObject(CLSID_Penguin, pPenguinCO, ...,
        &dwReg);
    if( SUCCEEDED(hr) ) {
        MSG msg; while( GetMessage(&msg, 0, 0, 0) ) DispatchMessage(&msg);
        CoRevokeClassObject(dwReg);
        pPenguinCO->Release();
    }

    CoUninitialize();
    return hr;
}

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:

template <class Base>                                           
class CComObjectGlobal : public Base {                          
public:                                                         
  ...                                                           
  STDMETHOD_(ULONG, AddRef )() { return _pAtlModule->Lock(); }   
  STDMETHOD_(ULONG, Release)() { return _pAtlModule->Unlock(); }
  ...                                                           
};                                                              

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:

// No references yet, so server not forced to stay alive
static CComObjectGlobal<CPenguinCO> g_penguinCO;

STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
    void** ppv) {
    // All references keep the server alive
    if( clsid == CLSID_Penguin )
        return g_penguinCO.QueryInterface(riid, ppv);
    return CLASS_E_CLASSNOTAVAILABLE;
}

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:

template <class Base>                                     
class CComObjectStack : public Base {                     
public:                                                   
    ...                                                   
    STDMETHOD_(ULONG, AddRef)()                            
    { ATLASSERT(FALSE); return 0; }                        
                                                           
    STDMETHOD_(ULONG, Release)()                           
    { ATLASSERT(FALSE); return 0; }                        
                                                           
    STDMETHOD(QueryInterface)(REFIID iid, void** ppvObject)
    { ATLASSERT(FALSE); return E_NOINTERFACE; }            
    ...                                                    
};                                                         

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:

void DoABadThing(IBird** ppbird) {
    CComObjectStack<CPenguin> penguin;
    penguin.Fly();           // Using IBird method is OK
    penguin.StraightenTie(); // Using ISnappyDresser method
                             // also OK

    // This will trigger an assert at runtime
    penguin.QueryInterface(IID_IBird, (void**)ppbird);
}

CComObjectStackEx addresses the limitations of CComObjectStack by providing a more useful implementation of IUnknown:

template <class Base>                                            
class CComObjectStackEx : public Base {                          
public:                                                          
    typedef Base _BaseClass;                                     
                                                                 
    CComObjectStackEx(void* = NULL) {                            
#ifdef _DEBUG                                                    
        m_dwRef = 0;                                             
#endif                                                           
        m_hResFinalConstruct = _AtlInitialConstruct();           
        if (SUCCEEDED(m_hResFinalConstruct))                     
            m_hResFinalConstruct = FinalConstruct();             
    }                                                            
                                                                 
    virtual ~CComObjectStackEx() {                               
        // This assert indicates mismatched ref counts.          
        //                                                       
        // The ref count has no control over the                 
        // lifetime of this object, so you must ensure           
        // by some other means that the object remains           
        // alive while clients have references to its interfaces.
        ATLASSUME(m_dwRef == 0);                                 
        FinalRelease();                                          
#ifdef _ATL_DEBUG_INTERFACES                                     
        _AtlDebugInterfacesModule.DeleteNonAddRefThunk(          
            _GetRawUnknown());                                   
#endif                                                           
    }                                                            
                                                                 
    STDMETHOD_(ULONG, AddRef)() {                                
#ifdef _DEBUG                                                    
        return InternalAddRef();                                 
#else                                                            
        return 0;                                                
#endif                                                           
    }                                                            
                                                                 
    STDMETHOD_(ULONG, Release)() {                               
#ifdef _DEBUG                                                    
        return InternalRelease();                                
#else                                                            
        return 0;                                                
#endif                                                           
    }                                                            
                                                                 
    STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {   
        return _InternalQueryInterface(iid, ppvObject);          
    }                                                            
                                                                 
    HRESULT m_hResFinalConstruct;                                
};                                                               

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:

void PlayWithBird() {
    CComObjectStackEx<CPenguin> penguin;
    IBird* pBird = NULL;
    penguin.QueryInterface(IID_IBird,
        (void**)&pBird);          // OK -> no assert
    DoBirdTrickes(pBird);
}

void DoBirdTricks(IBird* pBird) {
    pBird->Fly();                 // IBird methods OK
    ISnappyDresser* pPenguin = NULL;
    pBird->QueryInterface(IID_ISnappyDresser,
        (void**)&pPenguin);       // OK
    pPenguin->StraightenTie();    // ISnappyDresser methods OK
    pPenguin->Release();          // OK -> no assert
}

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

CcomObject

Standalone

Heap

Yes

Yes

Yes

CComAggObject

Aggregated

Heap

Yes

Yes

Yes

CComPolyObject

Standalone or aggregated

Heap

Yes

Yes

Yes

CComObjectCached

Standalone

Heap

No

Second Reference

Yes

CComObjectNoLock

Standalone

Heap

No

No

Yes

CComObjectGlobal

Standalone

Data seg.

No

Yes

Yes

CComObjectStack

Standalone

Stack

No

No

No

CComObjectStackEx

Standalone

Stack

No

No

Yes



previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)