Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

COM Concept : Unleashing Aggregation

0.00/5 (No votes)
30 Oct 2003 1  
Unleashing Aggregation

Introduction

Aggregation is one of the techniques to achieve reusability in COM. Aggregation is the specialized form of Containment, in which the programmer doesn�t have to implement code in the outer component to forward/delegate a call to the inner component and hence makes the life of a programmer easier. In Aggregation, the outer component hands over the control of the interface, which is being implemented by the inner component, to the client directly and the outer component gets out of the picture. In Aggregation, the interface to the inner component is exposed directly to the client, which is in contrast with the Containment.

This could lead to a problem, which can violate the basic QueryInterface rules, which has been stated below:

Handing over the interface pointer on the inner component can give a different view of the component because the client can call the QueryInterface on that interface pointer to get the IUnknown interface pointer on the inner component. The outer IUnknown and the Inner IUnknown will be having the different implementation of the QueryInterface and hence the different pictures will be portrayed to the client. The basic concept behind the aggregation is that the client should be independent of the implementation of the aggregation technique and client shouldn�t know that the outer component is aggregating the inner component. The outer IUnknown and the Inner IUnknown implements the different set of interfaces and hence gives the client a dual view of the component.

Details

Consider the following case: There are two components called CScientific and CBasic, which provide the functionality for scientific and basic mathematical operations respectively. The CBasic component exposes two interfaces called IAddSub and IMultiDiv. The CScientific class implements an interface ITrignometry interface to provide the trigonometric operations.

Now suppose, there are the clients who needs the scientific functionality along with the Addition and Subtraction operations of the basic functionality. The request of such a clients can be served by implementing the ITrignometry interface and aggregating the IAddSub interface. The implementation of the IAddSub is provided by the inner component and the outer component will be handing over the IAddSub interface pointer directly to the client for providing the services of the inner component. The outer component will not be delegating the IMultiDiv interface and hence it will not be visible to the client.

The beauty of the aggregation lies in the implementation of IUnknown interface on the inner component. The inner IUnknown shouldn�t be exposed to the client and hence the client should see only one IUnknown interface i.e. outer IUnknown (Controlling IUnknown).

The request for the IUnknown by calling a QueryInterface on the IAddSub interface pointer should return an IUnknown of an outer component and hence the inner component should make a use of the implementation provided by the outer component for the IUnknown.

Outer IUnknown to the inner component

To forward/delegate calls to the outer IUnknown, the inner component needs the outer component�s IUnknown interface pointer. The outer component passes its IUnknown interface pointer at the time of creating the inner component. The outer component calls CoCreateInstance and passes its IUnknown pointer in the second argument of CoCreateInstance. If this parameter is non-NULL, then the component is being aggregated, otherwise the component is not aggregated.

Inner component implementation

The inner component needs to implement two IUnknown interfaces to support the aggregation. The interface that will be controlling the lifetime of the inner component is called NonDelegating IUnknown and the interface, which forwards the calls on the IUnknown member functions, to the outer component, is called Delegating IUnknown.

The NonDelegating IUnknown is never exposed to the client and it is only the outer component, which can get a pointer to the NonDelegating IUnknown interface pointer. The outer component controls the lifetime of the inner component via NonDelegating interface pointer. Whenever a client asks for the IUnknown pointer, by calling the QueryInterface on the IAddSub interface pointer, the client should get the IUnknown pointer of the outer component. The implementation of NonDelegating IUnknown will be requiring the two IUnknown in the inner component and hence we will be defining the new interface called INonDelegateUnknown, which will be having the same virtual table layout as that of IUnknown. COM is all about the layout of the vtable layout and therefore the name of the member functions of INonDelegateUnknown will be prefixed with �NonDelegate�.

 struct INonDelegateUnknown {
      virtual HRESULT __stdcall NonDelegateQueryInterface ( const IID&, 
           void**)=0;
      virtual ULONG __stdcall NonDelegateAddRef()=0;
      virtual ULONG __stdcall NonDelegateRelease()=0;
  }; 

The NonDelegateAddRef and NonDelegateRelease implementation will increments and decrements the reference count of the inner component respectively.

The NonDelegateQueryInterface implementation needs to be modified so that whenever the outer component asks for the IUnknown interface pointer on the inner component, the inner component should hands over the NonDelegate IUnknown pointer to the outer component rather than IUnknown pointer. The outer component can get the NonDelegate IUnknown pointer on the inner component only at the time of the creation of the inner component and hence the NonDelegateQueryInterface will be called in the CreateInstance of the class object, which will corresponds to the inner component. The NonDelegateQueryInterface should performs the check for IUnknown interface and IAddSub interface because the outer component can get only these two interfaces from the inner component and for other interface pointer (i.e. IMultiDiv), the QueryInterface should return E_NOINTERFACE.

HRESULT __stdcall CBasic::NonDelegateQueryInterface (const IID& iid,
    void **ppv)
  {
      if (iid == IID_IUnknown) {

  // When the AddRef will be called on the ppv before returning

  // from this function,the reference count of the inner component

  // is incremented by 1.


             *ppv = static_cast < INonDelegateUnknown*>(this);
      }
      else if (iid = IID_IAddSub) {

  // When the AddRef will be called on the ppv before 

  // returning from this function,the reference count of the outer component

  // is incremented by 1. This is because that the IUnknown

  // implementation of IAddSub interface on inner component should 

  // delegate to the outer component�s controlling IUnknown implementation. 

             *ppv = static_cast < IAddSub*<(this);
      }
      else {
             *ppv = NULL;
             return E_NOINTERFACE;
      } 
    reinterpret_cast < IUnknown*<(*ppv)->AddRef();
    return S_OK;
  }

Why do we need to typecast this pointer into INonDelegateUnknown?

Typecasting this pointer ensures that the INonDelegateUnknown interface pointer is returned.

When the outer component queries for the interfaces, other than the INonDelegateUnknown pointer, belonging to the inner component, the reference count of the outer component is incremented. The reference count of the outer component will never reaches 0 and hence it will never be released from the memory. And therefore the outer component should call Release on its controlling IUnknown pointer, whenever it queries for any of the interfaces implemented by the inner component. Whenever an outer component queries for the INonDelegateUnknown pointer on the inner component, the reference count of the inner component is incremented.

The implementation of IClassFactory on the inner component�s class object is modified to pass the INonDelegateUnknown pointer to the outer component. The outer component can ask for only the IUnknown interface pointer at the time of the creation of the inner component because after its creation, all the QueryInterface calls will be delegated to the outer Unknown. The class factory needs to return a pointer to the nondelegating unknown and hence it will call NonDelegateQueryInterface in the implementation of CreateInstance.

Before executing the client application (AggregationClient.exe), the COM Servers (AggregationSample.dll & AggregableObject.dll) needs to be registered by regsvr32 utility. The code has been commented out properly to explain the crucial steps involved in the Aggregation technique.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here