COM containment and COM aggregation are the ways to implement reuse in COM. Both techniques are a means for getting several independent objects to behave as a single object with multiple interfaces. To the client, containment and aggregation look pretty much the same.
COM containment
COM containment, which is a has-a relationship, is the easier technique to figure out. To see how it works, let's look at an example. Imagine you've developed a COM-based spelling checker that dozens of clients have been clamoring for. You call your spelling checker class CoSpellChecker. It implements ISpellChecker. After selling your software to developers needing a spelling checker, you suddenly discover you can increase your sales by adding a thesaurus to your spelling checker services. The only problem is that you don't have time to write a thesaurus. However, you have found a vendor who provides a COM-based thesaurus called CoThesaurus that implements IThesurus. You'd like to add an implementation of IThesaurus to your spelling checker. Unfortunately, the thesaurus vendor doesn't want to give up the source code. So, how do you solve this problem? By integrating the CoThesaurus with CoSpellChecker.
One way to accomplish this is to expose the thesaurus interface as part of your object. Then have CoSpellChecker create CoThesaurus as part of its startup. When the client wants to use IThesaurus, CoSpellChecker simply forwards the thesaurus function calls to CoThesaurus. This method is pretty brainless-it's simply a matter of delegating method calls. Notice that this forwarding of method calls imposes a performance penalty. This doesn't make a lot of difference in an inproc situation, but it might make a great difference over a very long wire. The second way to implement binary reuse in COM is COM Aggregation.
COM aggregation
COM aggregation is a bit trickier to understand, mainly because it requires a certain amount of cooperation between the participating COM objects. COM aggregation depends on a contract between two or more COM objects. The first COM object is the controlling object. Then there are one or more participating objects. Both the controlling object and the participating objects have to perform a few special steps to make COM aggregation work.
Imagine you're developing the same COM object for performing spelling checks and thesaurus services. Implementing Ithesaurus using aggregation starts the same way as implementing the interface using containment-CoSpellChecker creates an instance of CoThesaurus. However, rather than forwarding the IThesaurus calls to CoThesaurus, CoSpellChecker actually passes CoThesaurus's IThesaurus out to the client, and the client actually uses the CoThesaurus interface.
The key idea here is that even though there are several objects participating in the aggregate, the client must see the aggregate as a single unit. That is, the IUnknown functions AddRef(), Release(), and QueryInterface() should apply to the aggregate as a whole. For example, a QueryInterface() for ISpellChecker through Ithesaurus even though the thesaurus object doesn't support ISpellChecker.
To get this to work, both objects must cooperate in the following way.
CoThesaurus maintains a copy of your IUnknown interface pointer, called the controlling IUnknown. This is critical for COM aggregation to work.
Whenever an instance of CoSpellChecker is created, it will in turn create an instance of CoThesaurus as part of the creation process. CoSpellChecker passes its IUnknown pointer to CoThesaurus.
When CoThesaurus receives the CoSpellChecker IUnknown pointer, it stores theCoSpellChecker's IUnknown pointer. CoThesaurus now holds a controlling IUnknown pointer.
When the client asks CoSpellChecker for the IThesaurus interface, CoSpellChecker simply passes out the CoThesaurus IThesaurus interface.