0% found this document useful (0 votes)
2 views

Aggregation & containment with source code

The document discusses the concepts of containment and aggregation in COM programming, highlighting their differences and implementation details. Containment allows for enhancing inner object behavior, while aggregation simplifies the outer object's interface management without reimplementing it. The document also provides code examples for both approaches and explains how to implement aggregation using ATL macros.

Uploaded by

deepak jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Aggregation & containment with source code

The document discusses the concepts of containment and aggregation in COM programming, highlighting their differences and implementation details. Containment allows for enhancing inner object behavior, while aggregation simplifies the outer object's interface management without reimplementing it. The document also provides code examples for both approaches and explains how to implement aggregation using ATL macros.

Uploaded by

deepak jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 13

Click here for larger image

For a long time, containment was considered unfashionable, and originally the ATL didn't support it, although
the ATL has always supported aggregation to some degree. The added opportunity with containment - that
you can enhance the inner object method behaviour in the outer object re-implementations - is also its added
risk.

Aggregation
Aggregation is a specialization of composition. When an outer object aggregates an interface of an inner
object, it doesn't reimplement the interface - it merely passes the inner object's interface pointer directly to
the client. So, it's less effort for the outer object. On the other hand, the outer object can't specialize the
behaviour of the inner object.
The goal of aggregation is to convince the client that an interface implemented by the inner object is actually
implemented by the outer object - the client should be unaware that there is more than one object in use.
However, the aggregation must be carefully managed so that the client can't use the IUnknown methods on
the inner object. For instance, since the client has a direct pointer to the inner object's IVehicle, the client
could use this pointer to call QueryInterface and ask for ICar, at which point the inner object would return
E_NOINTERFACE because it doesn't support ICar. This situation must be avoided in setting up aggregation.

Click here for larger image


Assuming you take care to accommodate the inherent schizophrenia in an aggregated object, aggregation is
a more elegant solution than containment, although there is one limitation: if MTS is looking after your
object, you can't use the object as part of an aggregate of non-MTS objects.

Containment
The following notes describe a very simple COM containment project, not using the ATL, just the raw COM
API. The code in ..\Vehicle is for a simple COM object, which will be used as the inner contained object. The
outer COM object is in ..\Car. A simple console client is in ..\Client. The ..\Registry directory contains some
shared registry-update code.
First, the significant code from the inner object, Vehicle. From the Vehicle.idl, you will see that the Vehicle
server offers just one interface, IVehicle, with one method, Drive:

[uuid(CBB27840-836D-11d1-B990-0080C824B323)
]
interface IVehicle : IUnknown
{
HRESULT Drive([in] int i, [in, out] int* ip);
};
The CVehicle class implements this interface:

class CVehicle : public IVehicle


{
public:
virtual HRESULT __stdcall QueryInterface(
const IID& riid, void** ppv);
virtual ULONG __stdcall AddRef(void);
virtual ULONG __stdcall Release(void);

virtual HRESULT __stdcall Drive(int i, int* ip)


{ (*ip) += i; return S_OK; }

CVehicle();
~CVehicle();
private:
long m_cRef;
};
The IUnknown methods, the class factory and the registry code is entirely as you would expect. So this inner
object offers nothing at all out of the ordinary - no special provisions for containment at all.
Now examine the code for the outer object. First the idl. Note the outer object's idl imports the inner object's
idl, and that the outer object supports both inner object and outer object interfaces. Note the directories
search path includes the path for the vehicle.idl.

// Car.idl
import "ocidl.idl";
import "Vehicle.idl";

[uuid(A9032A50-F54C-11d1-BCB6-0080C824B323)
]
interface ICar : IUnknown
{
HRESULT Reverse([in] int i, [in, out] int* ip);
};

[ uuid(C724E4D0-F54C-11d1-BCB6-0080C824B323),
version(1.0)
]
library Car
{
importlib("stdole2.tlb");

[ uuid(EA969C30-F54C-11d1-BCB6-0080C824B323),
]
coclass Car
{
[default] interface IVehicle;
interface ICar;
};
};
The CCar class implements both these interfaces, but note the crucial code in the outer object
implementation of the inner interface's Drive method - its just a wrapper to the inner object's
implementation. Note also the arbitrary Init function which will be used internally to initialize the inner object

class CCar : public IVehicle, public ICar


{
public:
virtual HRESULT __stdcall QueryInterface(
const IID& riid, void** ppv);
virtual ULONG __stdcall AddRef(void);
virtual ULONG __stdcall Release(void);

virtual HRESULT __stdcall Drive(int i, int* ip)


{ return m_pV->Drive(i, ip); }
virtual HRESULT __stdcall Reverse(int i, int* ip)
{ (*ip) -= i; return S_OK; }
HRESULT Init();

CCar();
~CCar();
private:
long m_cRef;
IVehicle* m_pV;
};
Given that we have a pointer to an inner object, the code in the constructor and destructor should be
common sense:

CCar::CCar() : m_cRef(1), m_pV(NULL)


{ InterlockedIncrement(&g_cObjects); }

CCar::~CCar()
{
InterlockedDecrement(&g_cObjects);
if (m_pV != NULL)
m_pV->Release();
}
The IUnknown methods are implemented in the normal way. The class factory is substantially ordinary, but
note the extra call in the CreateInstance to the Init method:

HRESULT __stdcall CCarFactory::CreateInstance (


IUnknown* pUnkOuter, const IID& riid, void** ppv)
{
if (pUnkOuter != NULL)
return CLASS_E_NOAGGREGATION;

CCar* pCar = new CCar;


if (pCar == NULL)
return E_OUTOFMEMORY;

HRESULT hr = pCar->Init();
if (FAILED(hr))
{
pCar->Release();
return hr;
}

hr = pCar->QueryInterface (riid, ppv);


pCar->Release();
return hr;
}
This Init function is implemented to create the inner object:

HRESULT CCar::Init()
{
HRESULT hr = ::CoCreateInstance(CLSID_Vehicle,
NULL,
CLSCTX_INPROC_SERVER,
IID_IVehicle,
(void**)&m_pV);
if (FAILED(hr))
return E_FAIL;
else
return S_OK;
}
Finally, the client. This is the expected runtime output:

Walk position = 3
Swim position = 0
The client application code follows. The client wants to use both IVehicle which offers Drive, and ICar which
offers Reverse. The client assumes that the Car object offers both interfaces. In order for this to work, we will
create a new Car object which internally makes use of the existing Vehicle object.

void main()
{
int i;
static int j = 0;
CoInitialize(NULL);
IVehicle* pv = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_Car,
NULL,
CLSCTX_INPROC_SERVER,
IID_IVehicle,
(void**)&pv);

if (SUCCEEDED(hr))
{
for (i = 1; i < 3; i++)
pv->Drive(i, &j);

cout << "position = " << j << endl;

ICar* pc = NULL;

hr = pv->QueryInterface(IID_ICar, (void**)&pc);

if (SUCCEEDED(hr))
{
for (i = 1; i < 3; i++)
pc->Reverse(i, &j);
cout << "position = " << j << endl;
pc->Release();
}
pv->Release();
}

CoUninitialize();
}

Aggregation
I'll now describe a very simple raw COM API aggregation project. The code in ..\ \Vehicle is for a simple COM
object, which will be used as the inner object in an aggregation. The outer COM object is in ..\ \Car. A simple
console client is in ..\Client. The ..\Registry directory contains some shared registry-update code. Examine Fig
3 below, which is a more detailed version of Fig 2.

Click here for larger image


First, the significant code from the inner object, Vehicle. From the Vehicle.idl, you will see that the Vehicle
server offers just one interface, IVehicle:

[uuid(CBB27840-836D-11d1-B990-0080C824B323)
]
interface IVehicle : IUnknown
{
HRESULT Drive([in] int i, [in, out] int* ip);
};
In the Vehicle.cpp, you will see another interface - this is not in the IDL and doesn't have a GUID because we
won't be publishing it - it is purely for internal use in the Vehicle server. This is exactly the same as the
standard IUnknown. Our inner object will effectively have two sets of the standard IUnknown methods. One
will be used to delegate to the outer object's IUnknown. The other - the Non-Delegating Unknown - will mostly
do the normal work of an unaggregated IUnknown.

interface INDUnknown
{
virtual HRESULT __stdcall NDQueryInterface(
REFIID riid, void **ppv) = 0;
virtual ULONG __stdcall NDAddRef() = 0;
virtual ULONG __stdcall NDRelease() = 0;
};
The Vehicle COM object is implemented in the CVehicle class. This derives from IVehicle (which is a COM
interface, and therefore derives from IUnknown), and from the unpublished INDUnknown. Hence, the
implementation of the IUnknown methods, the IVehicle methods and the INDUnknown methods. All three of
the IUnknown methods are implemented to delegate to the corresponding IUnknown methods of the outer
object, using the pointer to the outer interface.

class CVehicle : public IVehicle, public INDUnknown


{
public:
virtual HRESULT __stdcall QueryInterface(const IID& riid, void** ppv)
{ return m_pUnknownOuter->QueryInterface(riid, ppv); }
virtual ULONG __stdcall AddRef(void)
{ return m_pUnknownOuter->AddRef(); }
virtual ULONG __stdcall Release(void)
{ return m_pUnknownOuter->Release(); }

virtual HRESULT __stdcall NDQueryInterface(


const IID& riid, void** ppv);
virtual ULONG __stdcall NDAddRef(void);
virtual ULONG __stdcall NDRelease(void);

virtual HRESULT __stdcall Drive(int i, int* ip)


{ (*ip) += i; return S_OK; }

CVehicle(IUnknown* pUnknownOuter);
~CVehicle();

private:
long m_cRef;
IUnknown* m_pUnknownOuter;
};
The Non-Delegating AddRef and Release are implemented like normal IUnknown AddRef and Release, but the
INDUnknown::QueryInterface is different. The important difference is that requests for IUnknown are
unequivocally cast to the internal Non-Delegating INDUnknown - we don't want to return a pointer to our
inner normal IUnknown (because that delegates to the outer object).

HRESULT __stdcall CVehicle::NDQueryInterface(


const IID& riid, void** ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast<INDUnknown*>(this);
else if (riid == IID_IVehicle)
*ppv = static_cast<IVehicle*>(this);
else {
*ppv = NULL;
return E_NOINTERFACE;
}

reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
Now the outer object. Again, in the IDL, there's only one interface: [uuid(A9032A50-F54C-11d1-BCB6-
0080C824B323) ] interface ICar : IUnknown { HRESULT Reverse([in] int i, [in, out] int* ip); };
The outer COM Object is implemented in Car.cpp. Note the pointer to the inner interface:
class CCar : public ICar
{
public:
virtual HRESULT __stdcall QueryInterface(
const IID& riid, void** ppv);
virtual ULONG __stdcall AddRef(void);
virtual ULONG __stdcall Release(void);

virtual HRESULT __stdcall Reverse(int i, int* ip)


{ (*ip) -= i; return S_OK; }

HRESULT Init();

CCar();
~CCar();
private:
long m_cRef;
IUnknown* m_pUnknownInner;
};
The outer object's QueryInterface. When queried for IVehicle, we delegate to the inner object's interface.
However, remember that m_pUnknownInner is a pointer to the inner object's Non-Delegating INDUnknown
(which doesn't have a QueryInterface method - so how does this work?)

HRESULT __stdcall CCar::QueryInterface(const IID& riid, void** ppv)


{
if (riid == IID_IUnknown)
*ppv = static_cast<IUnknown*>(this);
else if (riid == IID_ICar)
*ppv = static_cast<ICar*>(this);
else if (riid == IID_IVehicle)
return m_pUnknownInner->QueryInterface(riid, ppv);
else {
*ppv = NULL;
return E_NOINTERFACE;
}

static_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
Here's the client. As you can see, the client creates a Car COM object, and expects the object to implement
both the ICar interface and the IVehicle interface, completely unaware of the aggregation:

void main()
{
int i;
static int j = 0;
CoInitialize(NULL);
IVehicle* pv = NULL;
HRESULT hr = ::CoCreateInstance(
CLSID_Car, NULL, CLSCTX_INPROC_SERVER,
IID_IVehicle, (void**)&pv);

if (SUCCEEDED(hr))
{
for (i = 1; i < 3; i++)
pv->Drive(i, &j);
cout << "position = " << j << endl;

ICar* pc = NULL;
hr = pv->QueryInterface(IID_ICar, (void**)&pc);
if (SUCCEEDED(hr))
{
for (i = 1; i < 3; i++)
pc->Reverse(i, &j);

cout << "position = " << j << endl;


pc->Release();
}
pv->Release();
}

CoUninitialize();
}

ATL Aggregation
From Visual C++, version 6, Aggregation is supported by default. When you use ATL to implement
aggregation, you use a series of ATL macros to implement the inner and outer object.

To implement the inner object

 Add the macro DECLARE_AGGREGATABLE to the class definition. If you use the v6 ATL Object Wizard
and check the aggregation box, this is already built-in, and in fact if you don't want aggregation you
must include the macro DECLARE_NOT_AGGREGATABLE instead.

To implement the outer object

 Declare the macro DECLARE_GET_CONTROLLING_UNKNOWN to define the function


GetControllingUnknown.. From v6, this is done for you. Without this function, the controlling
unknown cannot be passed to the inner object in the call to CoCreateInstance.
 Override the methods FinalConstruct and FinalRelease.
 In FinalConstruct you must call CoCreateInstance, passing the CLSID of the inner object you want to
create, and by requesting an IUnknown pointer back. Call the function GetControllingUnknown to
supply the inner object with the controlling IUnknown pointer.
 In FinalRelease, release the inner object's IUnknown.
 Add the COM_INTERFACE_ENTRY_AGGREGATE macro to the outer object's COM map, specifying the
inner object's interface and the IUnknown pointer on the inner object.
 Add the interface definition for the inner object to the .idl file of the outer object, and reference that
interface in the coclass.

Note: To ensure that the inner object does not do something while being created to release the outer object,
it is usually a good idea to declare the macro DECLARE_PROTECT_FINAL_CONSTRUCT. Again, v6 does this for
you. How could this be an issue? Well, consider this: the inner object might support only conditional
aggregation. In other words, it will allow itself to be aggregated only if the outer object attempting
aggregation supports some particular interface. So, the inner object would QI the outer object and only return
success on the CoCreateInstance if it likes the outer object's interfaces. You can see how easy it would be for
the inner object to accidentally release the outer object in such a scenario.

ATL Aggregation Macros


// atlcom.h
#define DECLARE_GET_CONTROLLING_UNKNOWN() public:\
virtual IUnknown* GetControllingUnknown() { return GetUnknown(); }
#define DECLARE_PROTECT_FINAL_CONSTRUCT()\
void InternalFinalConstructAddRef() { InternalAddRef(); }\
void InternalFinalConstructRelease() { InternalRelease(); }
From the folowing you can see that when an object is being aggregated, its created as a CComAggObject,
and when its being created independently, its created as a CComObject. If you want to disallow aggregation,
use DECLARE_NOT_AGGREGATABLE - for efficiency reasons, you should use this with EXE-packaged classes
(since aggregates must be in-process).

#define DECLARE_NOT_AGGREGATABLE(x) public:\


typedef CComCreator2< CComCreator< CComObject< x > >, \
CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
#define DECLARE_AGGREGATABLE(x) public:\
typedef CComCreator2< CComCreator< CComObject< x > >, \
CComCreator< CComAggObject< x > > > _CreatorClass;
If, for some strange reason, you want objects of your class to be created ONLY as part of an aggregation, use
DECLARE_ONLY_AGGREGATABLE:

#define DECLARE_ONLY_AGGREGATABLE(x) public:\


typedef CComCreator2< CComFailCreator<E_FAIL>, \
CComCreator< CComAggObject< x > > > _CreatorClass;
If you want to have your objects created from the same class whether they are being aggregated or not, use
DECLARE_POLY_AGGREGATABLE: this means your COM objects will always be created as objects of type
CComPolyObject:

#define DECLARE_POLY_AGGREGATABLE(x) public:\


typedef CComCreator< CComPolyObject< x > > _CreatorClass;
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);
}
};

These choices can be summarised like this:


Macro Normal Case Aggregated
Case
DECLARE_NOT_AGGREGATABLE CComObject Not supported
DECLARE_AGGREGATABLE CComObject CComAggObj
ect
DECLARE_ONLY_AGGREGATABLE Not supported CComAggObj
ect
DECLARE_POLY_AGGREGATABLE CComPolyObj CComPolyObj
ect ect
Project 1: ATL Inner Object and Client
In this simple project, we'll create a "Vehicle" COM object server where the object is suitable for aggregating
as an inner object (using the ATL from MSVC v6, the default COM object generated by the ATL Object Wizard
is already suitable for aggregation, so we don't have to do anything special at all). We will also write a client
that uses this object. Later, we will aggregate this Vehicle object as the inner object in an aggregation.
1. Create a new ATL COM AppWizard project, with all defaults. Insert a new ATL Object with the Object
Wizard. Give it the short name "Vehicle" and accept IVehicle as the default interface and CVehicle as
the class, etc. Leave everything default (ie dual interface, supports aggregation). Add one method to
the IVehicle interface, called Drive, with this parameter list:

2. [in] int i, [in, out] int* ip


3. In the CVehicle class, implement this function as shown below. Then build the server.

4. (*ip) += i;
5. return S_OK;
6. Now create another project - for the client. Make it a Win32 Console Application. Above main,
#import the Vehicle type library. Code main to test the Vehicle object, build and test.

7. try
8. {
9. IVehiclePtr pv(CLSID_Vehicle);
10.
11. for (i = 1; i < 3; i++)
12. pv->Drive(i, &j);
13.
14. cout << "position = " << j << endl;
15. }
16. catch (_com_error& e)
17. {
18. cout << e.ErrorMessage() << "\n";
19. }

Project 2: ATL Outer Object Aggregation


In this project, we'll create a "Car" COM object server where the object is suitable as the outer object in an
aggregation - for this, we'll need to do some extra work. The outer Car object will aggregate the inner Vehicle
object. We will also enhance our client to use the outer object and - transparently - also the aggregated
innner object.
1. Create a new ATL COM AppWizard project, with all defaults. Insert a new ATL Object with the Object
Wizard. Give it the short name "Car" and accept ICar as the default interface and CCar as the class,
etc. Again, leave everything default (ie dual interface, supports aggregation). Add a method to the
ICar interface, called Reverse, with the same parameter list as the IVehicle::Drive, ie:

2. [in] int i, [in, out] int* ip


3. In the CCar class, implement this method like this:

4. (*ip) -= i;
5. return S_OK;
6. Copy and paste the definition of the IVehicle interface from the ATLVehicle IDL file into the ATLCar
IDL file - put it above or below the ICar interface. In the library section, list the IVehicle interface as a
second supported interface in the Car coclass.
7. Declare a new member variable in the CCar class: an IUnknown pointer called m_pInner - this will
eventually hold the IUnknown pointer on the aggregated inner object. Initialize this to NULL in the
constructor. In the CCar class COM map, add an entry for IVehicle - this will be a special aggregation
macro that evaluates out to a QueryInterface through the inner object's IUnknown:
8. COM_INTERFACE_ENTRY_AGGREGATE(IID_IVehicle, m_pInner)
9. Also in the CCar class declaration, add the DECLARE_GET_CONTROLLING_UNKNOWN() macro - this
evaluates out to declaring a function called GetControllingUnknown, which will get the outer object's
IUnknown pointer. Also declare these two functions:

10. void FinalRelease();


11. HRESULT FinalConstruct();
12. In the Car's implementation file, first re-declare the CLSID for the Vehicle object:

13. const CLSID CLSID_Vehicle =


14. {0x5FD7754E,0xAE66,0x11D3,
15. {0x80,0xE9,0x00,0x60,0x08,0x43,0x8F,0x29}};

Note: You can copy this from the ATLVehicle_i.c. Then implement the two new functions as shown
below. The FinalConstruct will instantiate the inner object, using CoCreateInstance like any regular
client; and the FinalRelease will Release the inner object:

HRESULT CCar::FinalConstruct()
{
return CoCreateInstance(CLSID_Vehicle,
GetControllingUnknown(),
CLSCTX_ALL, IID_IUnknown,
(void**) &m_pInner);
}

void CCar::FinalRelease()
{
if (NULL != m_pInner)
m_pInner->Release();
}
16. Now, update the client: first change the #import to import the ATLCar type library and not the
ATLVehicle type library (remember the ATLCar type library will also expose the IVehicle interface).
Also change the client code to use the outer (and aggregated inner) object - in this way, the client is
only directly aware of the outer object, and assumes the outer object implements both IVehicle and
ICar. Build and test.

17. try
18. {
19. // IVehiclePtr pv(CLSID_Vehicle);
20. IVehiclePtr pv(CLSID_Car);
21. for (i = 1; i < 3; i++)
22. pv->Drive(i, &j);
23.
24. cout << "position = " << j << endl;
25.
26. ICarPtr pc = pv;
27. for (i = 1; i < 3; i++)
28. pc->Reverse(i, &j);
29.
30. cout << "position = " << j << endl;
31. }

Project 3: ATL Outer Object Containment


In this final project, we'll write a version of the "Car" COM object server where the object is suitable as the
outer object in a containment solution - the outer Car object will contain the inner Vehicle object. We'll also
enhance our client to use the outer object and the contained innner object.
1. Take a copy of your 3 earlier projects: the ATLVehicle, ATLCar and ATLClient. The inner Vehicle
project does not need to change - it is immaterial whether the inner object is being contained or
aggregated.
2. In the ATLCar IDL file, we could use the same technique as with aggregation (ie, redeclare the
IVehicle interface). Instead, import the Vehicle's IDL, by adding this line to the library section just
before the coclass:

3. import "..\ATLVehicle\ATLVehicle.idl";
4. Then list the IVehicle as a supported interface in the coclass as with aggregation.
5. Make sure the client code still #imports the outer object's type library:

6. #import "..\ATLCar.tlb" no_namespace named_guids


7. All the major work is done in the Car code. First, right-click on ATLCar classes, and select Implement
Interface, then select the ATLVehicle type library, and select the IVehicle interface, to get this code
in the Car header file:

8. #import "..\ATLVehicle\ATLVehicle.tlb" raw_interfaces_only, \


9. raw_native_types, no_namespace, named_guids

Note: By default, the high-level error-handling methods use the COM support classes _bstr_t and
variant_t in place of the BSTR and VARIANT data types and raw COM interface pointers. These
classes encapsulate the details of allocating and deallocating memory storage for these data types,
and greatly simplify type casting and conversion operations. The raw_native_types attribute is used
to disable the use of these COM support classes in the high-level wrapper functions, and force the
use of low-level data types instead.

The raw_interfaces_only attribute suppresses the generation of error-handling wrapper functions


and __declspec(property) declarations that use those wrapper functions.
...you'll also get this code in the multiple inheritance for the CCar class:

public IDispatchImpl<IVehicle,
&IID_IVehicle,
&LIBID_ATLVEHICLELib>
...and this code in the CCar class body:

STDMETHOD(Drive)(INT i, INT * ip)


{
return E_NOTIMPL;
}
10. If the wizard doesn't do it for you, you'll need to update the COM map (and don't just assume the
wizard will do it for you, because sometimes it doesn't):

11. COM_INTERFACE_ENTRY(IVehicle)
12. In the CCar class, add a new member variable, an IVehicle pointer called m_pVehicle. Initialize this
in the constructor to NULL. Now change the implementation of the Drive method to use this pointer
to delegate the Drive behaviour (remember, the CCar::Drive is only a stub to the CVehicle::Drive):

13. if (ip == NULL)


14. return E_POINTER;
15. return m_pVehicle->Drive(i, ip);
16. Also declare FinalConstruct and FinalRelease in the CCar class, and implement them as shown
below. Note: although these look superficially the same as the aggregation version, there are some
differences. Build and test.
17. HRESULT CCar::FinalConstruct()
18. {
19. return CoCreateInstance(CLSID_Vehicle,
20. NULL,
21. CLSCTX_INPROC_SERVER,
22. IID_IVehicle,
23. (void**) &m_pVehicle);
24. }
25.
26. void CCar::FinalRelease()
27. {
28. m_pVehicle->Release();
29. }

Summary
So, we've peered under the hood to look at the underlying mechanics of containment and
aggregation - and did you get the quintessentially COM way the two inner object unknowns are
managed? We then examined the way the ATL supports the two strategies. Which strategy you use
depends on what you're trying to do. Although containment is still not exactly fashionable, it is easy
to do and doesn't fall foul of other constraints like MTS hosting. Aggregation will always be faster,
although the internal code is a little trickier. Horses for courses.

You might also like