In Microsoft Component Object Model (COM), containment and delegation are two fundamental techniques used to manage relationships between COM objects, where one object (OuterCOMObj) delegates some of its responsibilities or functionality to another object (InnerCOMObj). These are common patterns used to achieve object composition, a form of code reuse, especially when an outer COM object (containing object) encapsulates one or more inner COM objects.
Containment: Containment is a composition technique where the OuterCOMObj contains an InnerCOMObj as a private member. The outer object uses the services of the inner object but does not expose the inner object directly to clients. In this scenario, the client is only aware of the outer object and interacts with it directly, while the outer object internally forwards some of the calls or functionality to the inner object. Key characteristics of containment:
Encapsulation: The OuterCOMObj hides the InnerCOMObj from the client. The client does not have direct access to the inner object or its interfaces.
Forwarding calls: The outer object might handle some calls itself and delegate specific tasks or responsibilities to the inner object by invoking its methods internally.
Separate identity: The InnerCOMObj has its own identity, meaning it has its own COM interface and reference count. However, it is invisible to the client since the client interacts only with the outer object.
Example:
The OuterCOMObj could represent a complex object like a document, while the InnerCOMObj represents a component of the document, such as a table. The outer object manages the entire document, but for operations on tables (like inserting a row), it calls methods on the inner object.
Implementation steps:
OuterCOMObj contains an instance of InnerCOMObj.
The outer object implements its own COM interfaces.
For some functionality, the outer object forwards requests to the inner object, but the client never directly interacts with the inner object.
Delegation:
Delegation is another technique where the OuterCOMObj directly delegates some of its interface's responsibilities to the InnerCOMObj. In this case, the outer object does not fully implement all the interfaces requested by the client but relies on the inner object to provide this functionality. However, the client is still unaware of the inner object and believes it is interacting only with the outer object.
Key characteristics of delegation:
Interface delegation: The outer object presents the interface to the client but forwards method calls to the inner object, which handles the actual implementation.
Unified identity: Although the inner object handles specific interface methods, the outer object maintains control over reference counting and overall object lifetime.
Client transparency: The client interacts with what appears to be the outer object, while behind the scenes, the outer object delegates some of the calls to the inner object.
Example:
If the OuterCOMObj implements a `IDocument` interface but uses the InnerCOMObj to handle specific `IPrint` functionality (like printing the document), the client still sees all functionality coming from the outer object. Calls related to printing are delegated to the inner object.
Implementation steps:
OuterCOMObj exposes an interface to the client.
When the client makes a call to this interface, the outer object delegates it to the inner object.
The client is unaware of this delegation and believes the outer object is handling everything.
Client Relationship between Outer and Inner COM Objects
In both containment and delegation, the OuterCOMObj is the primary point of contact for the client, and it manages the relationship with the InnerCOMObj. Whether through containment or delegation, the client is unaware of the existence of the inner object.
In containment, the outer object hides the inner object entirely, handling the forwarding of calls as necessary, whereas in delegation, the outer object effectively "borrows" the inner object's functionality to implement its interfaces, but it still appears as a single cohesive object to the client.
In summary:
Containment: focuses on the outer object managing an inner object that is hidden from the client.
Delegation: focuses on the outer object delegating certain tasks to the inner object, but still presents a unified interface to the client.
In a containment/delegation reuse scenario, the outer COM object (OuterCOMObj) establishes a client relationship with the inner COM object (InnerCOMObj). The outer COM object implements COM client code (for example, CoCreateInstance) to create an instance of the inner COM object, and obtains an interface pointer into one or more interfaces in the inner COM object.
This sets up the containment relationship.
Delegation
Additionally, the outer COM object delegates calls from some of its methods to methods in the inner COM object.
For example, assume the inner COM object implements interface IX1 with methods x1 and x2. The outer COM object can also expose interface IX1.
Because COM interfaces are immutable, the outer COM object's IX1 interface must implement the same methods as the inner COM object's IX1 interface.
As calls come into x1 and x2 in interface IX1 in OuterCOMObj, OuterCOMObj calls into
InnerCOMObj's IX1::x1 and IX1::x2.
The following diagram depicts the containment/delegation relationship between OuterCOMObj and InnerCOMObj.
From InnerCOMObj's perspective, OuterCOMObj is simply a COM client. InnerCOMObj has no idea that it is being reused.
From the client's perspective, OuterCOMObj appears to implement IX1.
The client cannot see InnerCOMObj. The outer COM object normally creates the inner COM object as part of its instantiation sequence. The outer COM object is responsible for the lifetime management of the inner COM object.
This means that the outer COM object must properly release all interface pointers it has into the inner COM object. This is normally done as part of the outer object's termination sequence.
Neither the client nor the inner COM object is aware of the containment/delegation.
Implementing Containment | Delegation
Containment/delegation allows some flexibility. For example, when the outer object exposes the same interface as the inner object, it can filter calls to the inner object by manipulating the method's parameters or augmenting the inner object's results.
The outer object can also choose not to delegate a call to the inner object.
For example, depending on a dynamic data set, OuterCOMObj's implementation of IX1::x1 may not call InnerCOMObj's implementation of IX1::x1.
Another variation of containment/delegation is that the outer object does not expose the same interface as the inner object. Instead, it delegates to methods in the inner COM object from a different interface.
For example, instead of implementing IX1, OuterCOMObj implements ZZ1 with methods z1 and z2.
In ZZ1::z1, OuterCOMObj delegates by calling InnerCOMObjIX1::x2.
COM Containment
COM containment is similar to C++ containment.
The outer component is simply a client of the inner components. The outer component implements its own interfaces using the inner components.
The clients of the outer component do not know that the outer component uses other components to carry out its services.
The outer component can also re-implement an interface supported by an inner component by forwarding calls to the inner component. The outer component might specialize the interface by adding scode before and after the code for the inner component.
Delegation Code - Exercise
Click the Exercise link below to apply what you've learned about containment/delegation. Delegation Code - Exercise