Lesson 9 | Aggregation guidelines: IUnknown information exchange |
Objective | List the guidelines for inner and outer IUnknown information exchange. |
Aggregation Guidelines - IUnknown information Exchange
Up to this point, we have looked at aggregation guidelines for both the inner and the outer objects.
The inner object needs the outer object's
IUnknown
pointer for use with its delegating
IUnknown
interface.
The outer object needs the inner object's nondelegating
IUnknown
pointer for interface navigation and to release the aggregated object.
The inner and outer COM objects need to exchange
IUnknown
pointers. That is exactly what the last aggregation guideline states:
The inner and outer objects must exchange IUnknown
pointers.
Specifically, the inner object gives the outer object its nondelegating
IUnknown
pointer,
receiving the outer object's
IUnknown
pointer in exchange. This happens when the outer object creates the inner object.
As part of its instantiation process, the outer object creates an instance of the inner object. The inner and outer objects exchange
IUnknown
pointers at this time.
CoCreateInstance and IClassFactory::CreateInstance take a parameter specifically dedicated to supporting aggregation. To aggregate an object, the outer object passes in a pointer to its
IUnknown
pointer and asks the inner object for its
IUnknown
pointer.
The aggregated (inner) object's class factory sees a request for aggregation in its implementation of
CreateInstance.
Aggregation: CoCreateInstance and IClassFactory::CreateInstance
When the outer object creates an instance of the inner object, it calls
IClassFactory::CreateInstance
directly or indirectly through
CoCreateInstance
. To indicate to the newly created COM object that the caller (the outer object) wants to aggregate it, both
IClassFactory::CreateInstance
and
CoCreateInstance
take an
IUnknown
pointer as a parameter.
This parameter is
NULL
when a client creates a COM object. For aggregation, the outer COM object passes in its
IUnknown
pointer. A non-
NULL
value in this parameter tells the newly created COM object that the caller wants to aggregate it.
Additionally, when an outer
IUnknown
pointer is passed in, the caller must ask for
IUnknown
that is,
the nondelegating
IUnknown
of the object.
HRESULT IClassFactory::CreateInstance(IUnknown *pIOuterIUnknown, REFIID riid, VOID **ppv);
- pIOuterIUnknown: Pointer to outer object's IUnknown if you want to aggregate to the new object. Null if not aggregated.
- REFIID riid: Must be IID_IUnknown if pIOuterUnknown is NOT null. i.e. when aggregating an object the outer object must pass in its IUnknown and it must ask for the inner object's IUnknown
- VOID **ppv: Pointer to hold requested interface pointer in. This will be IUnknown if we aggregate the object.
Helper function
CoCreateInstance
can be used in place of the calls to
CoGetClassObject
and
CreateInstance
:
STDAPI CoCreateInstance(REFCLSID objCLSID,IUnknown *pOuterIUnknown,
DWORD clsctx,REFIID riid,VOID **ppv);
- REFCLSID objCLSID: CLSID of COM Object whose class factory you want.
- IUnknown *pOuterIUnknown: Pointer to outer object's IUnknown. NULL if not aggregating.
- DWORD clsctx: CLSCTX_INPROC_SERVER
- REFIID riid: Must be IID_IUnknown if pOuterIUnknown is non-NULL.
- VOID **ppv); Pointer to hold requested interface pointer in. Will be IUnknown if the object is being aggregated.
A common implementation technique, in outer COM objects, is to provide a non-COM member function called
Init
. After creating an instance of the outer COM object, the object's
IClassFactory::CreateInstance
method calls
Init
. In
Init
, the outer COM object creates and aggregates the inner COM object.
Inside Microsoft Programming
Nonaggregatable COM Objects
A common clean-up technique is to release aggregated COM objects in a class destructor.
You can develop nonaggregatable COM objects by having the object's class factory return
CLASS_E_NOAGGREGATION
from
CreateInstance
.
You can code your COM objects in such a way that they can't be aggregated. The following pseudo-code demonstrates this:
class CInnerCOMObj : ... { ... }
class CInnerCOMObj_ClassFactory : public
IClassFactory {
...
HRESULT CreateInstance(IUnknown *pOuterIUnknown,
REFIID riid, VOID **ppv){
/* first check for aggregation,
is pOuterIUnknown non-NULL? */
if (pOuterIUnknown != NULL) {
//Return a no-aggregation error
return CLASS_E_NOAGGREGATION;
}
// Visual C++ Code
}
...
};
Implementing IClassFactory::CreateInstance in the inner object
A standard development practice is to have the C++ class that implements the inner COM object take in a pointer to the outer
IUnknown
in its constructor.
CreateInstance
will pass this into the constructor of the C++ class that implements the inner object. In its constructor, the COM object saves the outer object's pointer and uses it in all delegating
IUnknown
methods.
class CInnerCOMObj : ... {
public:
CInnerCOMObj(IUnknown *pOuterIUnknown) { ... }
...
};
The following pseudo-code demonstrates how the inner object class factory implements
IClassFactory::CreateInstance
:
class CInnerCOMObj_ClassFactory : public IClassFactory {
...
/* We do not have to specify CreateInstance as virtual and __stdcall.
We inherit these attributes from IClassFactory. */
HRESULT CreateInstance(IUnknown *pOuterIUnknown,REFIID riid,VOID **ppv) {
/* first check for aggregation - i.e. is pOuterIUnknown non-NULL */
if (pOuterIUnknown != NULL) {
/* now make sure they are asking for IUnknown. If not - fail the call */
if (riid != IID_IUnknown) {
return E_NOINTERFACE;
}
}
/* Now create the COM object - passing the outer object's IUnknown into it. NOTE: this
call will work for the non-aggregated case too - i.e. when pOuterIUnknown is NULL*/
CInnerCOMObj *pc = new CInnerCOMObj(pOuterIUnknown);
/* Call QueryInterface in the COM object to get the caller's requested interface */
HRESULT hr = pc->QueryInterface(riid, ppv);
if (FAILED(hr)) {
delete pc;
return hr;
}
return S_OK;
}
...
};
Notice how the creation and destruction of the inner objects and the outer object are kept synchronized. The outer object creates inner objects when it is created. The outer object will destroy inner objects when it is destroyed (recall from the previous lesson that the outer object releases aggregated objects when it is released). These steps, along with the inner object's delegating
IUnknown
interfaces and the outer object's uses of the aggregated object's nondelegating
IUnknown
interface, support the goal of aggregation: multiple objects combining into a composite object.
Analyze Inner Com Object - Exercise