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) {
*ppv = static_cast < INonDelegateUnknown*>(this);
}
else if (iid = IID_IAddSub) {
*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.