Dynamic Composition Techniques

[Previous] [Next]

We've just seen the most common way to implement COM classes: you first inherit a C++ class from some interfaces, and you then implement the union of the functions included in the interfaces. Of course, you need to update the interface map to ensure that the interfaces are actually exposed. With ATL, classes implemented via multiple inheritance of interfaces automatically follow the identity rules. Multiple inheritance isn't the only way to implement COM classes, however. Other ways to compose COM classes include implementing an interface as a tear-off interface or using aggregation. Let's start by looking at tear-off interfaces.

Tear-Off Interfaces

Imagine that your COM object has to conform to a higher-level protocol. Perhaps this protocol requires 15 interfaces formally, but your implementation can get away with implementing only 5 of them (say the other interfaces manage functionality that you and your client aren't interested in).

If you implement a COM class that uses classic multiple inheritance, the class is always going to lug around that extra vptr overhead. Tear-off interfaces are a mechanism for writing COM classes that create the resources necessary for implementing an interface on demand. In the CoSomeObject example illustrated in Figure 8-1, imagine that only the ISomeInterface interface needs to be fully implemented at all times and that IAnotherInterface is purely optional. So an independent C++ class implements IAnotherInterface. Calling QueryInterface to retrieve IAnotherInterface causes the CoSomeObject class to instantiate a new instance of the class implementing IAnotherInterface. Because the new tear-off class needs to implement IUnknown and preserve the object identity for the client, the new tear-off class gets a back pointer to the class with object identity (in this case, CoSomeObject). That way, the new tear-off class can forward QueryInterface callbacks to CoSomeObject and the client is happy because the COM object follows the rules of identity.

Figure 8-1 illustrates how tear-off interfaces work. AnotherInterfaceObj is shown in dashed lines to illustrate that it isn't created until the interface is requested.

click to view at full size.

Figure 8-1. CoSomeObject implemented using tear-off interfaces.

Tear-off interfaces are useful in two cases. They're useful whenever you have a large number of interfaces that are purely optional. They're also useful for interfaces that are transient—that is, interfaces with pointers that don't stick around for very long.

Using tear-off interfaces has a certain size cost. The COM-related size overhead of CoSomeObject implemented using normal multiple inheritance of interfaces is 12 bytes: 4 bytes for each interface vptr—ISomeInterface and IAnotherInterface—and 4 more bytes for a reference counter. The size of a multiple inheritance COM class goes up by 4 bytes for every interface it implements. However, the COM-related size of CoSomeObject using tear-off interfaces is 20 bytes (when implemented using raw C++ and with all interfaces in use). This includes 4 bytes for the ISomeInterface vptr, 4 bytes for CoSomeObject's reference count, and 12 bytes for the tear-off class. Each tear-off class needs a back pointer to the managing object, 4 bytes for the vptr, and 4 more bytes for the reference counter. Each additional tear-off class takes up 12 whole bytes. The size of a COM class with tear-off interfaces goes up by 12 bytes for every interface it implements as a tear-off. As a result, the overhead of tear-off interfaces tends to be larger if the interfaces are queried for frequently.

Now that you see how raw tear-off interfaces work, let's take a look at how ATL implements tear-off interfaces.

Tear-Offs and ATL

ATL supports two kinds of tear-off interfaces—plain, noncached tear-off interfaces and cached tear-off interfaces. ATL's classes for implementing tear-off interfaces include CComTearOffObjectBase, CComTearOffObject, and CComCachedTearOffObject. A COM class implementing an interface as a noncached tear-off creates a new instance of the subobject each time the interface is requested. The subobject implementing the tear-off interface stays alive as long as its reference count is greater than 0. A COM class implementing an interface as a cached tear-off creates a new instance of the subobject once, the first time the interface is requested, and caches the object, using the same subobject every time that interface is requested. Tear-off interfaces are interesting because they make heavy use of ATL's creator architecture, which we'll briefly review next. Then we'll examine both noncached and cached tear-offs in more detail.

ATL's Creators

When you pump out an ATL class using the ATL COM Object Wizard, ATL applies IUnknown functions appropriate for the threading model of your class. However, even though your class derives from a class with functions that effectively implement IUnknown, that's not enough framework to give your class an IUnknown implementation. Remember that the job of IUnknown is to provide clients with an interface negotiation mechanism. In addition, IUnknown gives the class a way to handle object and server lifetime (via AddRef and Release). QueryInterface, AddRef, and Release work differently depending on whether the object is tear-off, aggregated, stand-alone, and so on. Also, the construction policy of the object varies depending on how the object is used. ATL lets you defer adding the right implementation of IUnknown until the very last minute.

When you write ATL classes, you mix in the correct implementation of IUnknown via one of several classes, such as CComObject, CComTearOffObject, CComAggObject, or CComPolyObject. These classes hide the construction mechanism, depending on one of the ATL creator classes to handle construction. That is, the construction policy for a certain class is hidden. The creator classes have a static function named CreateInstance for implementing the construction mechanism. Rather than use operator new directly to handle object construction, ATL relies on the creator classes to mix in the correct behavior. This functionality lets you apply the boilerplate code for aggregation as a mix-in capability to your class. Listing 8-1 shows the main creator class, named CComCreator.

Listing 8-1. The CComCreator class.

 template <class T> class CComCreator { public:     static HRESULT WINAPI     CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {         HRESULT hRes = E_OUTOFMEMORY;         T* p = new T(pv);         if(p != NULL) {              p->InternalFinalConstructAddRef();             hRes = p->FinalConstruct();             p->InternalFinalConstructRelease();             if(hRes == S_OK)                 hRes = p->QueryInterface(riid, ppv);             if(hRes != S_OK)                  delete p;         } return hRes;     } }; 

Notice how CComCreator is a templatized class that accepts a parameter that indicates the type of object to create. Object creation time happens to be the same time your code decides on the aggregation or tear-off status. There are several COM creator helper classes: CComObject, CComAggObject, CComTearOffObject, CComCachedTearOffObject, and CComPolyObject. Each supports COM identity and lifetime issues involved in implementing tear-offs and aggregation. Applying CComTearOffObject to your class involves your object in a tear-off scenario. Applying CComAggObject to your class makes your object support only aggregation. Applying CComObject to your class means that your object doesn't support COM aggregation. Applying CComPolyObject to your class means that your class supports either kind of instantiation.

To work with a creator class to the C++ COM class, you apply your class wrapped by CComObject to one of the creator classes, like this:

 typedef ComCreator<CComObject<CSomeObject>>  _CreatorClass; 

Then you can create instances of CSomeObject, like this:

 ISomeInterface* ppv; CSomeObject::_CreatorClass::CreateInstance(NULL,     IID_ISomeInterface, ppv); 

ATL uses this _CreatorClass later on in the object map. We'll see that shortly.

We've learned that ATL includes a creator abstraction. But this still doesn't explain how ATL handles identity issues such as tear-off and aggregation. For that we need to review how ATL implements IClassFactory.

ATL and IClassFactory

ATL provides an implementation of IClassFactory named CComClassFactory. CComClassFactory has a creation function pointer as a data member. This pointer is named m_pfnCreateInstance. The function's signature looks like this:

 HRESULT FUNC(void* pv, REFIID riid, LPVOID* ppv); 

The function pointed to by this data member is used to create instances of classes. ATL creators are useful for building this function for use in ATL's CComClassFactory implementation of IClassFactory. Just as with MFC, ATL has macros for adding a class factory to your class. The macro is named DECLARE_CLASSFACTORY. You use it from within your class to add class factory support. Here's how the DECLARE_CLASSFACTORY macro expands:

 typedef     CComCreator<CComObjectCached<CComClassFactory>>     _ClassFactoryCreatorClass; 

Notice how DECLARE_CLASSFACTORY uses the CComCreator class to hide the creation process. Also notice how the macro uses the CComObjectCached class as the helper class. This helper class is similar to CComObject except that it manages a single cached object instead of creating new objects.

ATL's tear-off implementation uses this creator architecture to hide the creation policy behind objects. Let's dig in and see how ATL implements tear-off interfaces.

Noncached Tear-Offs

ATL implements its tear-off interfaces in two phases—the CComTearOffObjectBase methods handle the reference count and QueryInterface, and CComTearOffObject implements IUnknown. CComTearOffObject implements a tear-off interface as a separate object that is instantiated only when that interface is queried for. The tear-off is deleted when its reference count becomes 0.

As a developer, you only need to concern yourself with the CComTearOffObjectBase class, as you need to derive your tear-off object from CComTearOffObjectBase. CComTearOffObjectBase is templatized on the threading model and the owner object (the class with object identity). Listing 8-2 shows the definition of CComTearOffObjectBase.

Listing 8-2. The CComTearOffObjectBase class.

 template <class Owner, class ThreadModel = CComObjectThreadModel>     class CComTearOffObjectBase :     public CComObjectRootEx<ThreadModel> { public:     typedef Owner _OwnerClass;     CComObject<Owner>* m_pOwner;     CComTearOffObjectBase() {m_pOwner = NULL;} }; 

Notice how CComTearOffObjectBase includes a back pointer to the class with object identity (the data member m_pOwner). CComTearOffObjectBase uses this data member to maintain the COM identity laws (to call QueryInterface on the owner class).

Implementing a tear-off interface using ATL is fairly straightforward. You simply create a tear-off class using CComTearOffObjectBase. Then you create the main class (the one with object identity) in the normal way. The tear-off magic happens within the interface map. All you need to do now is use the COM_INTERFACE_ENTRY_TEAR_OFF macro in the interface map.

When a client calls QueryInterface for the auxiliary interface, the COM_INTERFACE_ENTRY_TEAR_OFF macro creates a new tear-off object using the normal ATL creation mechanism. The macro uses the CComTearOffObject class during the creation process. Here's the COM_INTERFACE_ENTRY_TEAR_OFF macro:

 #define COM_INTERFACE_ENTRY_TEAR_OFF(iid, x)\     {&iid,\     (DWORD)&_CComCreatorData<\         CComInternalCreator< CComTearOffObject< x > >\         >::data,\     _Creator}, 

This macro takes the interface associated with the tear-off along with the class implementing that interface. When ATL encounters a COM_INTERFACE_ENTRY_TEAR_OFF macro in the interface map, ATL uses the associated tear-off creator to produce the tear-off object. The following code shows how to implement a tear-off interface in ATL:

 class CCoreObject; class CAuxilliary :      public IUseThisRarely,     public CComTearOffObjectBase<CCoreObject> {  public:        CAuxilliary() {     }     STDMETHOD(YouDontCallMeEnough)() {         return S_OK;     }        BEGIN_COM_MAP(CAuxilliary)        COM_INTERFACE_ENTRY(IUseThisRarely)        END_COM_MAP() }; ///////////////////////////////////////////////////////////////// // CCoreObject class ATL_NO_VTABLE CCoreObject :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CCoreObject, &CLSID_CoreObject>,     public IDispatchImpl<ICoreObject, &IID_ICoreObject,\         &LIBID_TEAROFFSLib>,     public IUseThisALot { public:     CCoreObject()     {     } DECLARE_REGISTRY_RESOURCEID(IDR_COREOBJECT) BEGIN_COM_MAP(CCoreObject)     COM_INTERFACE_ENTRY(ICoreObject)     COM_INTERFACE_ENTRY(IDispatch)     COM_INTERFACE_ENTRY_TEAR_OFF(IID_IUseThisRarely,          CAuxilliary) END_COM_MAP() // ICoreObject public:     STDMETHOD(Method2)();     STDMETHOD(Method1)(); // IUseThisALot public:     STDMETHOD(YouCallMeTooMuch)(); }; 

Cached Tear-Off Interfaces

Implementing a cached tear-off interface in ATL is very similar to implementing a noncached one. The only difference is that you use the COM_INTERFACE_ENTRY_CACHED_TEAR_OFF macro in the interface map. The difference between the cached and noncached tear-off macros is that the cached tear-off macro uses the CComCachedTearOffObject class when creating the tear-off interface. The COM_INTERFACE_ENTRY_CACHED_TEAR_OFF macro looks like this:

 #define COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid,      x, punk)\     {&iid,\     (DWORD)&_CComCacheData<\         CComCreator<CComCachedTearOffObject<x>>,\     (DWORD)offsetof(_ComMapClass, punk)\         >::data,\     _Cache}, 

To create a cached tear-off, the auxiliary class inherits from CComCachedTearOffObject. CComCachedTearOffObject implements IUnknown for a tear-off interface. CComCachedTearOffObject maintains its own reference count on its IUnknown and deletes itself when its reference count is 0. Querying for any of the subobject's interfaces increments the reference count of the owner object's IUnknown.

When an interface implemented using ATL's cached tear-off interface mechanism is requested, the subobject is instantiated only once. Subsequent requests use the same tear-off object (as opposed to a tear-off interface implemented by a CComTearOffObject, which instantiates multiple tear-off objects).

The caveat with cached interfaces is that the class with object identity must implement FinalRelease and call Release on the cached IUnknown for the CComCachedTearOffObject. This decrements the tear-off's reference count and causes the cached tear-off to delete itself.

That does it for source code tear-off interfaces. Let's finish this chapter with a look at how ATL performs the cruel identity hack known as COM aggregation.

COM Aggregation (aka Binary Tear-Offs)

Before launching into aggregation, we'd like to clear the air about the purpose behind COM aggregation. You'll usually find the topic of COM aggregation listed in COM textbooks under the heading of "binary reuse." Long ago, when COM was making its public debut, C++ developers mumbled implementation inheritance mantras and lived the implementation inheritance lifestyle, which became popular during the late 1980s and early 1990s. Because COM doesn't support implementation inheritance (as C++ does), COM took a lot of bashing. Folks were calling COM some unflattering names, all because COM didn't support implementation inheritance.

Classic COM documentation (including Microsoft's original COM spec) mentioned COM aggregation as a substitute for run-time binary implementation inheritance. COM aggregation doesn't really solve this problem. Instead, COM aggregation is a way for a COM object to acquire an interface from some other COM object and pass it off as its own interface. However, although aggregation is a way to reuse COM code at the binary level, aggregation makes better sense when viewed as another way to leverage the identity laws. In fact, another way of thinking about aggregation is to consider it as a technique for implementing binary tear-off interfaces. Recall that tear-off interfaces are a way for COM objects to provide interfaces on demand (thereby letting objects avoid carrying all the vptr data around all the time).

As with tear-off implementations, understanding COM identity is key to understanding COM aggregation. Here's the general gist of COM aggregation. Imagine you're implementing the CoSomeObject for some client. You know how to implement ISomeInterface just fine. However, you don't know how to implement IAnotherInterface very well. You want to leverage (leverage is the '90s word for stealing) someone else's implementation. So you license someone else's binary implementation of IAnotherInterface (because nobody wants to give away his or her source code for the implementation of IAnotherInterface).

To use the binary implementation of IAnotherInterface, CSomeObject creates an instance of the AnotherObject object using the normal COM creation mechanism—CoCreateInstance. In calling CoCreateInstance, the CSomeObject passes in its IUnknown pointer as the second parameter to CoCreateInstance. In this case, CoSomeObject is the controlling object. This IUnknown pointer passed into the AnotherObject object serves as the back pointer to CSomeObject (the class with object identity).

When CSomeObject creates an instance of the AnotherObject object, AnotherObject passes back its nondelegating IUnknown (the traditional IUnknown that does the regular QueryInterface lookup). In addition, the AnotherObject object holds on to the back pointer (an IUnknown pointer) to the CSomeObject. When the client asks for IAnotherInterface by calling QueryInterface through CSomeObject's IUnknown or ISomeInterface interface, the CSomeObject can't produce the interface, so CSomeObject calls QueryInterface through the AnotherObject's nondelegating IUnknown pointer (the traditional interface lookup mechanism). At this point, the client acquires a working implementation of IAnotherInterface. Here's where COM identity steps in.

When the client calls QueryInterface through IAnotherInterface asking for ISomeInterface, AnotherObject needs to be able to produce the ISomeInterface interface. The only way for AnotherObject to be able to do this is to forward the IUnknown calls back to the controlling object (CSomeObject). The QueryInterface, AddRef, and Release functions provided by AnotherObject map to AnotherObject's delegating IUnknown functions. That is, the IUnknown functions the external client sees simply forward back to CoSomeObject's IUnknown functions.

Figure 8-2 illustrates COM aggregation. As you can see in the figure, AnotherObj, the aggregatee, maintains the normal IUnknown functions. When AnotherObj is participating in aggregation, however, only the aggregator uses the traditional query interface call, when it creates the aggregatee. When in aggregation mode, AnotherObj forwards all external IUnknown method calls back to the controlling object—the one with the identity. The controlling object uses AnotherObj's QueryInterface if the controlling object can't produce the requested interface.

click to view at full size.

Figure 8-2. CoSomeObject implemented using COM aggregation.

For many developers, understanding COM aggregation is like understanding recursion. If you stare at it long enough, it starts to make sense. COM aggregation requires both parties to agree to a few rules and to implement several lines of boilerplate code. The key to understanding COM aggregation is to understand COM identity. Let's take a look at how ATL handles COM aggregation.

ATL and Aggregation

ATL adds aggregation support to your object via a simple radio button selection. When you ask the ATL COM Object Wizard to create a COM class for you, the wizard presents the dialog box shown in Figure 8-3.

You can create your COM classes as aggregatable (the default), nonaggregatable, or aggregatable-only. (They can't be created as stand-alone objects.) These radio buttons basically determine the ATL object's construction policy, which is hidden behind ATL's creators as described earlier.

Figure 8-3. Options available through the ATL COM Object Wizard.

The following code shows a plain-vanilla COM class produced by the Object Wizard when the aggregation radio button is checked.

 class ATL_NO_VTABLE CAggObj :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CAggObj, &CLSID_AggObj>,     public IDispatchImpl<IAggObj, &IID_IAggObj, &LIBID_AGGTESTLib> { public:     CAggObj() {} DECLARE_REGISTRY_RESOURCEID(IDR_AGGOBJ) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CAggObj)     COM_INTERFACE_ENTRY(IAggObj)     COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() }; 

Although you can't see any explicit aggregation support within this generated class, aggregation support is there—it's available by deriving from CComCoClass. Deriving from CComCoClass gives you default class object and instance creators. CComCoClass includes these macros:

 DECLARE_CLASSFACTORY() DECLARE_AGGREGATABLE(T) // Your class is passed in as a                         //  template parameter. 

We've already seen how the DECLARE_CLASSFACTORY macro works. But how does the DECLARE_AGGREGATABLE macro work? Here's the macro:

 #define DECLARE_AGGREGATABLE(x) public:\     typedef CComCreator2<CComCreator<CComObject<x>>,          CComCreator<CComAggObject<x>>>          _CreatorClass; 

Notice that DECLARE_AGGREGATABLE defines a type named _CreatorClass, which is made up of a CComCreator2 template class that uses your class type as a parameter. Listing 8-3 shows the CComCreator2 class.

Listing 8-3. The CComCreator2 class.

 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);     } }; 

CComCreator2 accepts two template parameters, class T1 and class T2. The CComCreator2 class has a CreateInstance function that accepts a pointer to a controlling unknown, an IID, and a pointer to a pointer—the regular CreateInstance signature. The CreateInstance function uses either the first or second template parameter, depending on whether the back pointer has a value. Look at the DECLARE_AGGREGATABLE macro shown above. If the class is aggregatable, the creation mechanism chooses between the regular COM creator (using the CComObject helper class) or the aggregation creator (using the CComAggObject helper class). This mixes in the correct behavior for the class factory, depending on whether the object is being created as an aggregate or as a stand-alone object.

Polyaggregatable Objects

If you want to create a single class that either can be aggregatable or can run standing alone, use CComPolyObject. When an instance of CComPolyObject is created, the value of the outer unknown is checked. If the outer unknown is NULL, IUnknown is implemented for a nonaggregated object. If the outer unknown is not NULL, IUnknown is implemented for an aggregated object.

If you use CComPolyObject instead of CComAggObject, you get a class that handles both composition options. Then you don't have to use both CComAggObject and CComObject inside your module to handle the aggregated and nonaggregated cases. This means that only one copy of the vtable and one copy of the functions exist in your module. If your vtable is large, this optimization can substantially decrease your module size. If your vtable is small, however, using CComPolyObject can result in a slightly larger module size because it's not optimized for an aggregated or nonaggregated object, as are CComAggObject and CComObject.

Here's the DECLARE_POLY_AGGREGATABLE macro:

 #define DECLARE_POLY_AGGREGATABLE(x) public:\     typedef CComCreator<CComPolyObject<x>> _CreatorClass; 

This macro defines a single creator class that creates CComPolyObject based on your class.

In addition to aggregatable classes, ATL supports aggregatable-only classes as well as nonaggregatable classes.

Aggregatable-Only Classes

Aggregatable-only classes are generated using the DECLARE_ONLY_AGGREGATABLE macro:

 #define DECLARE_ONLY_AGGREGATABLE(x) public:\     typedef CComCreator2<CComFailCreator<E_FAIL>,          CComCreator<CComAggObject<x>>>          _CreatorClass; 

DECLARE_ONLY_AGGREGATABLE defines a _CreatorClass that fails if the object is created in stand-alone mode. The CComFailCreator class mentioned above has a CreateInstance method that simply fails—no if, ands, or buts.

Nonaggregatable Classes

Nonaggregatable classes are generated using the DECLARE_NOT_AGGREGATABLE macro:

 #define DECLARE_NOT_AGGREGATABLE(x) public:\     typedef CComCreator2<CComCreator<CComObject<x>>,         CComFailCreator<CLASS_E_NOAGGREGATION>>          _CreatorClass; 

DECLARE_NOT_AGGREGATABLE defines a _CreatorClass that fails if the object is created as an aggregate. With ATL, it's fairly easy to choose an aggregation policy by selecting from one of several macros. The other part of aggregation occurs within the outer object—or the class that manages the object's identity.

The Outer Objects

The outer object is the final point we'll cover in our examination of how ATL implements aggregates. The first step is getting an inner object that the outer object can aggregate to. This is really just a matter of calling CoCreateInstance in the right place. One option is to create the inner object manually in the ATL class's final constructor function and then release the object's interface pointer in the FinalRelease function, as shown here:

 class COuterObject : public IOuter {     HRESULT FinalConstruct() {         return CoCreateInstance(CLSID_CAgg,             GetControllingUnknown(), // Below             CLSCTX_INPROC_SERVER,             IID_IUnknown,             (void**)&m_punkInner);     }     FinalRelease() {         m_punkAgg.Release();         m_punkAutoAgg.Release();     }     void    FinalRelease();     DECLARE_PROTECT_FINAL_CONSTRUCT()     DECLARE_GET_CONTROLLING_UNKNOWN()     CComPtr<IUnknown> m_punkInner; }; 

Using the DECLARE_GET_CONTROLLING_UNKNOWN macro declares a GetControllingUnknown function for getting a pointer to pass as the second parameter of CoCreateInstance. The DECLARE_PROTECT_FINAL_CONSTRUCT protects your object from being deleted if the internal aggregated object increments the reference count and then decrements the count to 0 during FinalConstruct.

The last step is to make sure that the interface map is filled with correct macros for performing interface lookup. You have two options for managing COM aggregation. The first option is for the outer object to perform controlled aggregation. That is, the controlling outer object filters out interface requests before calling QueryInterface on the inner object. The second option is to have the outer object perform blind aggregation. In other words, you can have the outer object blindly forward interface requests to the inner object.

In addition to the option of controlled vs. blind aggregation, you can have the controlling outer object create the inner object or you can have ATL create the inner object for you. ATL provides the following four interface macros for performing each kind of aggregation:

  • Controlled aggregation in which the outer object manages the aggregated object
  • Blind aggregation in which the outer object manages the aggregated object
  • Controlled aggregation in which ATL automatically creates the aggregated object
  • Blind aggregation in which ATL automatically creates the aggregated object

Let's first examine normal aggregation, in which the outer object controls interface requests and creates the inner object.

COM_INTERFACE_ENTRY_AGGREGATE

COM_INTERFACE_ENTRY_AGGREGATE is the first aggregation-oriented macro. This macro enables ordinary aggregation.

 #define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\     {&iid,\     (DWORD)offsetof(_ComMapClass, punk),\     _Delegate}, 

COM_INTERFACE_ENTRY_AGGREGATE takes the interface ID as the first parameter and an IUnknown pointer as the second parameter. When ATL encounters this code in an object's interface map during QueryInterface, ATL forwards the request to the IUnknown pointer represented in the second parameter. This is, of course, assumed to be the IUnknown pointer to the inner object.

COM_INTERFACE_ENTRY_AGGREGATE_BLIND

If you want to let the inner object have at the interface request without having the outer object censor it, put the COM_INTERFACE_ENTRY_AGGREGATE_BLIND macro in your interface map:

 #define COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk)\     {NULL,\     (DWORD)offsetof(_ComMapClass, punk),\     _Delegate}, 

Unlike COM_INTERFACE_ENTRY_AGGREGATE, this macro doesn't take an interface ID as a parameter. When ATL hits this entry in an interface map, ATL blindly forwards interface requests to the inner object.

COM_INTERFACE_ENTRY_AUTOAGGREGATE

If you don't want to go through the trouble of creating the inner object and you want ATL to create it for you, use the COM_INTERFACE_ENTRY_AUTOAGGREGATE macro in your interface map:

 #define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid)\     {&iid,\     (DWORD)&_CComCacheData<\         CComAggregateCreator<_ComMapClass, &clsid>,\         (DWORD)offsetof(_ComMapClass, punk)\         >::data,\     _Cache}, 

Notice that in addition to an interface ID and an unknown pointer, this macro has a third parameter that is a class ID. If the unknown pointer is NULL, ATL uses the class ID supplied to the macro to create the inner class.

COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND

You use COM_INTERFACE_ENTRY_AUTOAGGREGATE when you want ATL to create the inner object for you and you also want to forward all interface requests to the inner object. Here's the COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND macro:

 #define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid)\     {NULL,\     (DWORD)&_CComCacheData<\         CComAggregateCreator<_ComMapClass, &clsid>,\         (DWORD)offsetof(_ComMapClass, punk)\         >::data,\     _Cache}, 

Like COM_INTERFACE_ENTRY_AGGREGATE_BLIND, COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND forwards interface requests to the inner object. The difference between COM_INTERFACE_ENTRY_AGGREGATE_BLIND and COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND is that the latter macro creates the inner object if the unknown pointer is NULL.

Aggregation Example

To aggregate to another object in the regular way, add the COM_INTERFACE_ENTRY_AGGREGATE macro as shown here:

 class COuter : public IOuter,      BEGIN_COM_MAP(COuter)         COM_INTERFACE_ENTRY(IOuter)         COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_punkInner)     END_COM_MAP()  }; 

In addition to having the outer object create the inner object during object startup, your other option is to have the object create the inner class only when the inner interface is queried for, using the COM_INTERFACE_ENTRY_AUTOAGGREGATE macro in the interface map. Using this macro causes the COM object to be created on demand.

As long as the correct entries are in the interface map, ATL will find them when clients call QueryInterface and the client will get a pointer to the aggregated object.



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

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