Agrégation

L’agrégation est le mécanisme de réutilisation d’objet dans lequel l’objet externe expose les interfaces de l’objet interne comme si elles étaient implémentées sur l’objet externe lui-même. Cela est utile lorsque l’objet externe délègue chaque appel à l’une de ses interfaces à la même interface dans l’objet interne. L’agrégation est disponible à titre pratique pour éviter une surcharge d’implémentation supplémentaire dans l’objet externe dans ce cas. L’agrégation est en fait un cas spécialisé de confinement/délégation.

L’agrégation est presque aussi simple à implémenter que le confinement, à l’exception des trois fonctions IUnknown : QueryInterface, AddRef et Release. Le problème est que, du point de vue du client, toute fonction IUnknown sur l’objet externe doit affecter l’objet externe. Autrement dit, AddRef et Release affectent l’objet externe et QueryInterface expose toutes les interfaces disponibles sur l’objet externe. Toutefois, si l’objet externe expose simplement l’interface d’un objet interne comme étant la sienne, les membres IUnknown de cet objet interne appelés via cette interface se comportent différemment de ceux de ces membres IUnknown sur les interfaces de l’objet externe, une violation absolue des règles et propriétés régissant IUnknown.

La solution est que l’agrégation nécessite une implémentation explicite de IUnknown sur l’objet interne et la délégation des méthodes IUnknown de toute autre interface aux méthodes IUnknown de l’objet externe.

Création d’objets agrégables

La création d’objets pouvant être agrégés est facultative ; cependant, il est simple à faire et offre des avantages significatifs. Les règles suivantes s’appliquent à la création d’un objet agrégable :

  • L’implémentation de l’objet agrégable (ou interne) de QueryInterface, AddRef et Release pour son interface IUnknown contrôle le nombre de références de l’objet interne, et cette implémentation ne doit pas déléguer à l’objet externe inconnu ( IUnknown de contrôle).
  • L’implémentation de l’objet agrégable (ou interne) de QueryInterface, AddRef et Release pour ses autres interfaces doit déléguer à l’IUnknown de contrôle et ne doit pas affecter directement le nombre de références de l’objet interne.
  • L’IUnknown interne doit implémenter QueryInterface uniquement pour l’objet interne.
  • L’objet aggregable ne doit pas appeler AddRef lors de la conservation d’une référence au pointeur IUnknown de contrôle.
  • Lorsque l’objet est créé, si une interface autre que IUnknown est demandée, la création doit échouer avec E_NOINTERFACE.

Le fragment de code suivant illustre une implémentation correcte d’un objet aggregable à l’aide de la méthode de classe imbriquée d’implémentation d’interfaces :

// 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; 
        }; 
}; 
 

Agrégation d’objets

Lors du développement d’un objet agrégable, les règles suivantes s’appliquent :

  • Lors de la création de l’objet interne, l’objet externe doit demander explicitement son IUnknown.

  • L’objet externe doit protéger son implémentation de Release contre la réentrance avec un nombre de références artificielles autour de son code de destruction.

  • L’objet externe doit appeler sa méthode IUnknownRelease de contrôle s’il recherche un pointeur vers l’une des interfaces de l’objet interne. Pour libérer ce pointeur, l’objet externe appelle sa méthode AddRef IUnknown de contrôle, suivie de Release sur le pointeur de l’objet interne.

    // Obtaining inner object interface pointer 
    pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); 
    pUnkOuter->Release(); 
    
    // Releasing inner object interface pointer 
    pUnkOuter->AddRef(); 
    pISomeInterface->Release(); 
    
    
  • L’objet externe ne doit pas déléguer aveuglément une requête pour une interface non reconnue à l’objet interne, sauf si ce comportement est spécifiquement l’intention de l’objet externe.

Confinement/délégation