彙總
匯總是物件重複使用機制,其中外部物件會公開內部物件的介面,就像是在外部物件本身上實作一樣。 當外部物件將其中一個介面的呼叫委派給內部物件中的相同介面時,這會很有用。 在此案例中,匯總是方便使用,以避免外部對象的額外實作額外負荷。 匯總實際上是內含專案/委派的特殊案例。
匯總幾乎就像實作內含項目一樣簡單,但三個 IUnknown 函式除外:QueryInterface、AddRef 和 Release。 catch 是從客戶端的觀點來看,外部物件上的任何 IUnknown 函式都必須影響外部物件。 也就是說, AddRef 和 Release 會影響外部物件, QueryInterface 會公開外部物件上可用的所有介面。 不過,如果外部物件只是將內部物件的介面公開為本身,則透過該介面呼叫的內部物件的 IUnknown 成員的行為會與外部物件介面上的 IUnknown 成員不同,絕對違反管理 IUnknown 的規則和屬性。
解決方案是匯總需要在內部物件上明確實作 IUnknown,並將任何其他介面的 IUnknown 方法委派給外部物件的 IUnknown 方法。
建立可匯總的對像是選擇性的;不過,這樣做很簡單,而且提供顯著的好處。 下列規則適用於建立可匯總的物件:
- 可匯總的 (或 inner) 物件的 QueryInterface、AddRef 和 Release 的 IUnknown 介面會控制內部對象的參考計數,而且這個實作不得委派給外部物件的未知 (控制 IUnknown)。
- 可匯總的 (或 inner) 物件的 QueryInterface、AddRef 和 Release,其其他介面的實作必須委派給控制 IUnknown,而且不得直接影響內部對象的參考計數。
- 內部 IUnknown 必須只針對內部物件實作 QueryInterface。
- 匯總物件在持有控制 IUnknown 指標的參考時,不得呼叫 AddRef。
- 建立物件時,如果要求 IUnknown 以外的任何介面,則建立作業必須失敗,且E_NOINTERFACE。
下列代碼段說明使用實作介面的巢狀類別方法,正確實作可匯總的物件:
// 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;
};
};
開發可匯總物件時,會套用下列規則:
建立內部物件時,外部對象必須明確要求其 IUnknown。
外部對象必須保護其 Release 的實作,避免使用其解構碼周圍的人工參考計數重新進入。
如果外部對象查詢任何內部物件介面的指標,外部對象必須呼叫其控制 IUnknownRelease 方法。 為了釋放這個指標,外部物件會呼叫其控制 IUnknownAddRef 方法,後面接著內部物件的指標上的 Release。
// Obtaining inner object interface pointer pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); pUnkOuter->Release(); // Releasing inner object interface pointer pUnkOuter->AddRef(); pISomeInterface->Release();
外部物件不得盲目地將任何無法辨識介面的查詢委派給內部物件,除非該行為是外部物件的意圖。