Aggregation
Aggregation ist der Wiederverwendungsmechanismus für Objekte, bei dem das äußere Objekt Schnittstellen aus dem inneren Objekt verfügbar macht, als ob sie im äußeren Objekt selbst implementiert wären. Dies ist nützlich, wenn das äußere Objekt jeden Aufruf an eine seiner Schnittstellen an dieselbe Schnittstelle im inneren Objekt delegiert. Aggregation ist als Komfort verfügbar, um in diesem Fall zusätzlichen Implementierungsaufwand im äußeren Objekt zu vermeiden. Aggregation ist eigentlich ein spezieller Fall von Containment/Delegierung.
Aggregation ist fast so einfach zu implementieren wie Containment, mit Ausnahme der drei IUnknown-Funktionen : QueryInterface, AddRef und Release. Der Haken ist, dass aus Sicht des Clients jede IUnknown-Funktion auf dem äußeren Objekt das äußere Objekt beeinflussen muss. Das heißt, AddRef und Release wirken sich auf das äußere Objekt aus, und QueryInterface macht alle Schnittstellen verfügbar, die für das äußere Objekt verfügbar sind. Wenn das äußere Objekt jedoch einfach die Schnittstelle eines inneren Objekts als eigene verfügbar macht, verhalten sich die IUnknown-Member dieses inneren Objekts, die über diese Schnittstelle aufgerufen werden, anders als die IUnknown-Member auf den Schnittstellen des äußeren Objekts, was ein absoluter Verstoß gegen die Regeln und Eigenschaften für IUnknown ist.
Die Lösung besteht darin, dass die Aggregation eine explizite Implementierung von IUnknown für das innere Objekt und die Delegierung der IUnknown-Methoden einer beliebigen anderen Schnittstelle mit den IUnknown-Methoden des äußeren Objekts erfordert.
Das Erstellen von Objekten, die aggregiert werden können, ist optional. Dies ist jedoch einfach und bietet erhebliche Vorteile. Die folgenden Regeln gelten für das Erstellen eines aggregierbaren Objekts:
- Die Implementierung von QueryInterface, AddRef und Release für die IUnknown-Schnittstelle des aggregierbaren (oder inneren) Objekts steuert die Verweisanzahl des inneren Objekts, und diese Implementierung darf nicht an das Unbekannte des äußeren Objekts delegieren (das steuernde IUnknown).
- Die Implementierung von QueryInterface, AddRef und Release für die anderen Schnittstellen des aggregierbaren (oder inneren) Objekts muss an das steuernde IUnknown delegiert werden und darf sich nicht direkt auf die Verweisanzahl des inneren Objekts auswirken.
- Das innere IUnknown muss QueryInterface nur für das innere Objekt implementieren.
- Das aggregable-Objekt darf AddRef nicht aufrufen, wenn ein Verweis auf den steuernden IUnknown-Zeiger gehalten wird.
- Wenn beim Erstellen des Objekts eine andere Schnittstelle als IUnknown angefordert wird, muss die Erstellung mit E_NOINTERFACE fehlschlagen.
Das folgende Codefragment veranschaulicht eine korrekte Implementierung eines aggregable-Objekts mithilfe der geschachtelten Klassenmethode der Implementierung von Schnittstellen:
// CSomeObject is an aggregable object that implements
// IUnknown and ISomeInterface
class CSomeObject : public IUnknown
{
private:
DWORD m_cRef; // Object reference count
IUnknown* m_pUnkOuter; // Controlling IUnknown, no AddRef
// Nested class to implement the ISomeInterface interface
class CImpSomeInterface : public ISomeInterface
{
friend class CSomeObject ;
private:
DWORD m_cRef; // Interface ref-count, for debugging
IUnknown* m_pUnkOuter; // Controlling IUnknown
public:
CImpSomeInterface() { m_cRef = 0; };
~ CImpSomeInterface(void) {};
// IUnknown members delegate to the outer unknown
// IUnknown members do not control lifetime of object
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{ return m_pUnkOuter->QueryInterface(riid,ppv); };
STDMETHODIMP_(DWORD) AddRef(void)
{ return m_pUnkOuter->AddRef(); };
STDMETHODIMP_(DWORD) Release(void)
{ return m_pUnkOuter->Release(); };
// ISomeInterface members
STDMETHODIMP SomeMethod(void)
{ return S_OK; };
} ;
CImpSomeInterface m_ImpSomeInterface ;
public:
CSomeObject(IUnknown * pUnkOuter)
{
m_cRef=0;
// No AddRef necessary if non-NULL as we're aggregated.
m_pUnkOuter=pUnkOuter;
m_ImpSomeInterface.m_pUnkOuter=pUnkOuter;
} ;
~CSomeObject(void) {} ;
// Static member function for creating new instances (don't use
// new directly). Protects against outer objects asking for
// interfaces other than Iunknown.
static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void **ppv)
{
CSomeObject* pObj;
if (pUnkOuter != NULL && riid != IID_IUnknown)
return CLASS_E_NOAGGREGATION;
pObj = new CSomeObject(pUnkOuter);
if (pObj == NULL)
return E_OUTOFMEMORY;
// Set up the right unknown for delegation (the non-
// aggregation case)
if (pUnkOuter == NULL)
{
pObj->m_pUnkOuter = (IUnknown*)pObj ;
pObj->m_ImpSomeInterface.m_pUnkOuter = (IUnknown*)pObj;
}
HRESULT hr;
if (FAILED(hr = pObj->QueryInterface(riid, (void**)ppv)))
delete pObj ;
return hr;
}
// Inner IUnknown members, non-delegating
// Inner QueryInterface only controls inner object
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
*ppv=NULL;
if (riid == IID_IUnknown)
*ppv=this;
if (riid == IID_ISomeInterface)
*ppv=&m_ImpSomeInterface;
if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);
((IUnknown*)*ppv)->AddRef();
return NOERROR;
} ;
STDMETHODIMP_(DWORD) AddRef(void)
{ return ++m_cRef; };
STDMETHODIMP_(DWORD) Release(void)
{
if (--m_cRef != 0)
return m_cRef;
delete this;
return 0;
};
};
Beim Entwickeln eines aggregierbaren Objekts gelten die folgenden Regeln:
Beim Erstellen des inneren Objekts muss das äußere Objekt explizit nach seinem IUnknown fragen.
Das äußere Objekt muss seine Implementierung von Release vor Wiedereinbruch mit einer künstlichen Verweisanzahl um seinen Zerstörungscode schützen.
Das äußere Objekt muss seine steuernde IUnknownRelease-Methode aufrufen, wenn es einen Zeiger auf eine der Schnittstellen des inneren Objekts abfragt. Um diesen Zeiger freizugeben, ruft das äußere Objekt seine steuernde IUnknownAddRef-Methode auf, gefolgt von Release für den Zeiger des inneren Objekts.
// Obtaining inner object interface pointer pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); pUnkOuter->Release(); // Releasing inner object interface pointer pUnkOuter->AddRef(); pISomeInterface->Release();
Das äußere Objekt darf eine Abfrage für eine unbekannte Schnittstelle nicht blind an das innere Objekt delegieren, es sei denn, dieses Verhalten ist ausdrücklich die Absicht des äußeren Objekts.