Condividi tramite


TN038: implementazione di IUnknown MFC/OLE

Nota

La seguente nota tecnica non è stata aggiornata da quando è stata inclusa per la prima volta nella documentazione online.Di conseguenza, alcune procedure e argomenti potrebbero essere non aggiornati o errati.Per le informazioni più recenti, è consigliabile cercare l'argomento di interesse nell'indice della documentazione online.

OLE 2 è basato su "OLE Component Object Model" o COM. COM definisce uno standard per la modalità di comunicazione degli oggetti che interagiscono Ciò include i dettagli dell'aspetto di un oggetto, inclusa la modalità di invio dei metodi in un oggetto. COM definisce inoltre una classe base, da cui vengono derivate tutte le classi COM compatibili. Questa classe di base è IUnknown. Benché l'interfaccia IUnknown venga definita classe C++, COM non è specifico di un linguaggio: può essere implementato in C, PASCAL o in qualsiasi altro linguaggio in grado di supportare il layout binario di un oggetto COM.

OLE fa riferimento a tutte le classi derivate da IUnknown come "interfacce." Si tratta di una distinzione importante, poiché un'interfaccia come IUnknown non include alcuna implementazione. Definisce semplicemente il protocollo con cui gli oggetti comunicano, non le specifiche di ciò che eseguono tali implementazioni. Ciò è valido per un sistema che consente la massima flessibilità. È compito di MFC implementare un comportamento predefinito per i programmi di MFC/C++.

Per comprendere l'implementazione di MFC di IUnknown, è necessario conoscere questa interfaccia. Una versione semplificata di IUnknown è definita di seguito:

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Nota

Alcuni dettagli necessari della convenzione di chiamata, come __stdcall per questa illustrazione vengono omessi.

Le funzioni membro AddRef e Release controllano la gestione della memoria dell'oggetto. COM utilizza uno schema di conteggio dei riferimenti per tenere traccia degli oggetti. Non viene mai fatto direttamente riferimento a un oggetto nello stesso modo in cui avviene in C++. Viceversa, agli oggetti COM viene fatto riferimento sempre tramite un puntatore. Per rilasciare l'oggetto al termine dell'utilizzo da parte del proprietario, viene chiamato il membro Release dell'oggetto, anziché utilizzare l'operatore delete come avverrebbe per un oggetto C++ tradizionale. Il meccanismo di conteggio dei riferimenti consente la gestione di più riferimenti a un singolo oggetto. Un'implementazione di AddRef e Release gestisce un conteggio dei riferimenti all'oggetto: l'oggetto non viene eliminato fino a quando il conteggio dei riferimenti non raggiunge lo zero.

AddRef e Release sono piuttosto semplici da un punto di vista dell' implementazione. Di seguito viene illustrata un'implementazione molto semplice:

ULONG CMyObj::AddRef() 
{ 
    return ++m_dwRef; 
}

ULONG CMyObj::Release() 
{ 
    if (--m_dwRef == 0) 
    {
        delete this; 
        return 0;
    }
    return m_dwRef;
}

La funzione membro QueryInterface è un po' più interessante. Non è particolarmente interessante avere un oggetto di cui le sole funzioni membro siano AddRef e Release. Sarebbe preferibile configurare l'oggetto affinché esegua più operazioni di quante ne fornisca IUnknown. Per questo è utile QueryInterface. Consente di ottenere una "interfaccia" diversa nello stesso oggetto. Queste interfacce vengono solitamente derivate da IUnknown e aggiungono altre funzionalità mediante l'aggiunta di nuove funzioni membro. Le interfacce COM non includono mai variabili membri dichiarate nell'interfaccia e tutte le funzioni membro vengono dichiarate come virtuali pure. Di seguito è riportato un esempio:

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

Per ottenere un oggetto IPrintInterface se si dispone solo di un oggetto IUnknown, chiamare QueryInterface utilizzando IID di IPrintInterface. IID corrisponde a un numero a 128 bit che identifica in modo univoco l'interfaccia. È presente un IID per ogni interfaccia definita dall'utente o da OLE. Se pUnk è un puntatore a un oggetto IUnknown, il codice per il recupero di IPrintInterface potrebbe essere:

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, 
    (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();   
        // release pointer obtained via QueryInterface
}

Sembra piuttosto semplice, ma come si implementa un oggetto che supporta sia IPrintInterface che l'interfaccia IUnknown? In questo caso è semplice poiché IPrintInterface deriva direttamente da IUnknown, implementando IPrintInterface, IUnknown è supportato automaticamente. Di seguito è riportato un esempio.

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

Le implementazioni di AddRef e Release sono esattamente uguali a quelle implementate in precedenza. CPrintObj::QueryInterface potrebbe risultare simile al seguente:

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

Come si può notare, se l'identificatore di interfaccia (IID) viene riconosciuto, un puntatore viene restituito all'oggetto. In caso contrario, si verifica un errore. Si noti inoltre che un QueryInterface riuscito comporta un AddRef implicito. Naturalmente, è anche necessario implementare CEditObj::Print. Ciò è semplice perché l'interfaccia IPrintInterface è stata derivata direttamente dall'interfaccia IUnknown. Tuttavia, se si desidera supportare due interfacce diverse, entrambe derivate da IUnknown, si consideri quanto segue:

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

Anche se esistono vari modi differenti di implementare una classe che supporta sia IEditInterface che IPrintInterface, tra cui l'utilizzo dell'ereditarietà multipla di C++, questa nota si concentrerà sull'utilizzo delle classi annidate per implementare questa funzionalità.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

Di seguito è inclusa l'intera implementazione:

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef() 
{ 
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CEditObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

ULONG CEditPrintObj::CPrintObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CPrintObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

Si noti che la maggior parte dell'implementazione di IUnknown è posizionata nella classe CEditPrintObj anziché duplicare il codice in CEditPrintObj::CEditObj e in CEditPrintObj::CPrintObj. Questo riduce la quantità di codice ed evita i bug. Il punto chiave qui è che dall'interfaccia IUnknown è possibile chiamare QueryInterface per recuperare qualsiasi interfaccia che l'oggetto possa supportare e da ciascuna di tali interfacce è possibile recuperare altre interfacce. Ciò significa che tutte le funzioni QueryInterface) disponibili da ogni interfaccia devono comportarsi esattamente allo stesso modo. Affinché questi oggetti incorporati possano chiamare l'implementazione nell'"oggetto esterno", viene utilizzato un puntatore all'indietro (m_pParent). Il puntatore m_pParent viene inizializzato durante il costruttore CEditPrintObj. Occorre quindi implementare anche CEditPrintObj::CPrintObj::PrintObject and CEditPrintObj::CEditObj::EditObject. È stato aggiunto un frammento di codice per aggiungere una funzionalità, vale a dire la possibilità di modificare l'oggetto. Fortunatamente, raramente le interfacce dispongono di una sola funzione membro (anche se è possibile che si verifichi) e in questo caso, EditObject e PrintObject verranno di solito combinati in una singola interfaccia.

Uno scenario di questa semplicità richiede molte spiegazioni e molto codice. Le classi MFC/OLE forniscono un'alternativa più semplice. Nell'implementazione MFC viene utilizzata una tecnica simile alla modalità di wrapping dei messaggi Windows con le mappe messaggi. Questa funzionalità è denominata mappe dell'interfaccia ed è illustrata nella sezione successiva.

Mappe dell'interfaccia MFC

MFC/OLE include un'implementazione di "mappe dell'interfaccia" simile alle "mappe messaggi" e alle "mappe invio" di MFC a livello di concetto ed esecuzione. Le funzionalità principali delle mappe dell'interfaccia di MFC sono le seguenti:

  • Implementazione standard di IUnknown, compilata nella classe CCmdTarget.

  • Manutenzione del conteggio dei riferimenti, modificato da AddRef e Release

  • Implementazione di QueryInterface) basata sui dati

Inoltre, le mappe dell'interfaccia supportano le seguenti funzionalità avanzate:

  • Supporto per la creazione di oggetti COM aggregabili

  • Supporto per l'utilizzo di oggetti di aggregazione nell'implementazione di un oggetto COM

  • L'implementazione è associabile ed estensibile

Per ulteriori informazioni sull'aggregazione, vedere l'argomento Aggregazione.

Il supporto della mappa dell'interfaccia di MFC è radicato nella classe CCmdTarget. CCmdTarget "dispone di un" conteggio dei riferimenti, nonché di tutte le funzioni membro associate all'implementazione IUnknown (il conteggio dei riferimenti ad esempio si trova in CCmdTarget). Per creare una classe che supporta OLE COM, derivare una classe da CCmdTarget e utilizzare varie macro nonché funzioni membro di CCmdTarget per implementare le interfacce desiderate. L'implementazione di MFC utilizza le classi annidate per definire ogni implementazione dell'interfaccia molto simile all'esempio precedente. Ciò è semplificato con un'implementazione standard di IUnknown nonché di varie macro che eliminano parte del codice ripetitivo.

Per implementare una classe utilizzando le mappe dell'interfaccia di MFC

  1. Derivare una classe direttamente o indirettamente da CCmdTarget.

  2. Utilizzare la funzione DECLARE_INTERFACE_MAP nella definizione della classe derivata.

  3. Per ogni interfaccia che si desidera supportare, utilizzare le macro END_INTERFACE_PART e BEGIN_INTERFACE_PART nella definizione della classe.

  4. Nel file di implementazione utilizzare le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP per definire la mappa dell'interfaccia della classe.

  5. Per ogni IID supportato, utilizzare la macro INTERFACE_PART tra le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP per associare tale IID a una "parte" specifica della classe.

  6. Implementare ciascuna delle classi annidate che rappresentano le interfacce supportate.

  7. Utilizzare la macro METHOD_PROLOGUE per accedere all'elemento padre, l'oggetto derivato da CCmdTarget.

  8. AddRef, Releasee QueryInterface) possono delegare all'implementazione CCmdTarget di queste funzioni (ExternalAddRef, ExternalRelease e ExternalQueryInterface).

L'esempio precedente di CPrintEditObj può essere implementato come segue:

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

La suddetta dichiarazione crea una classe derivata da CCmdTarget. La macro DECLARE_INTERFACE_MAP indica al framework che questa classe disporrà di una mappa personalizzata dell'interfaccia. Inoltre, le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART definiscono le classi annidate, in questo caso con i nomi CEditObj e CPrintObj (la X viene utilizzata solo per distinguere le classi annidate dalle classi globali che iniziano con "C " e dalle classi di interfaccia che iniziano con "I". Vengono creati due membri annidati di queste classi: m_CEditObj e m_CPrintObj, rispettivamente. Le macro dichiarano automaticamente le funzioni AddRef, Release e QueryInterface; pertanto si dichiarano solo le funzioni specifiche a questa interfaccia: EditObject e PrintObject (la macro OLE STDMETHOD viene utilizzata in modo che _stdcall e le parole chiave virtuali siano considerate appropriate per la piattaforma di destinazione).

Per implementare la mappa dell'interfaccia per questa classe:

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

Ciò connette IID_IPrintInterface a m_CPrintObj e IID_IEditInterface a m_CEditObj, rispettivamente. L'implementazione CCmdTarget di QueryInterface (CCmdTarget::ExternalQueryInterface) utilizza questa mappa per restituire i puntatori a m_CPrintObj e m_CEditObj quando richiesti. Non è necessario includere una voce per IID_IUnknown; il framework utilizzerà la prima interfaccia nella mappa, in questo caso m_CPrintObj, quando IID_IUnknown è richiesto.

Anche se nella macro BEGIN_INTERFACE_PART sono state automaticamente dichiarate le funzioni AddRef, Release e QueryInterface, è necessario implementarle:

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

L'implementazione per CEditPrintObj::CPrintObj sarebbe simile alle suddette definizioni per CEditPrintObj::CEditObj. Sebbene sarebbe possibile creare una macro che potrebbe essere utilizzata per generare automaticamente queste funzioni (questo è avvenuto in precedenza nello sviluppo di MFC/OLE), è difficile impostare punti di interruzione quando una macro genera più righe di codice. Per questo motivo, questo codice viene espanso manualmente.

Tramite l'implementazione del framework delle mappe dei messaggi, esistono numerose operazioni che non era necessario eseguire:

  • Implementare QueryInterface

  • Implementare AddRef e versione

  • Dichiarare uno di questi metodi incorporati in entrambe le interfacce

Inoltre, il framework utilizza mappe messaggi internamente. Ciò consente di derivare da una classe del framework, ad esempio COleServerDoc, che supporta già determinate interfacce e fornisce sostituzioni o aggiunte alle interfacce fornite dal framework. Questa operazione è possibile in quanto il contenuto del framework supporta completamente l'ereditarietà di una mappa dell'interfaccia da una classe base. Questo è il motivo per cui BEGIN_INTERFACE_MAP accetta come secondo parametro il nome della classe base.

Nota

In genere non è possibile riutilizzare l'implementazione delle implementazioni predefinite di MFC delle interfacce OLE semplicemente ereditando la specializzazione incorporata di tale interfaccia dalla versione di MFC.Ciò non è possibile in quanto l'utilizzo della macro METHOD_PROLOGUE per accedere all'oggetto contenitore derivato da CCmdTarget implica un offset fisso dell'oggetto incorporato dall'oggetto derivato da CCmdTarget.Ciò significa, ad esempio, che non è possibile derivare un oggetto XMyAdviseSink incorporato dall'implementazione di MFC in COleClientItem::XAdviseSink, poiché XAdviseSink si basa sul fatto di trovarsi in corrispondenza di un offset specifico dalla parte superiore dell'oggetto COleClientItem.

Nota

È possibile, tuttavia, delegare all'implementazione MFC tutte le funzioni che si desiderano per il comportamento predefinito di MFC.Questa operazione viene eseguita nell'implementazione MFC di IOleInPlaceFrame (XOleInPlaceFrame) nella classe COleFrameHook (delega a m_xOleInPlaceUIWindow per molte funzioni).Questa progettazione è stata scelta per ridurre le dimensioni di runtime degli oggetti che implementano molte interfacce; elimina la necessità di un puntatore all'indietro (ad esempio la modalità di utilizzo di m_pParent nella sezione precedente).

Mappe dell'interfaccia e di aggregazione

Oltre a supportare gli oggetti COM autonomi, MFC supporta anche l'aggregazione. L'aggregazione di per sé è un argomento troppo complesso da discutere qui. Per ulteriori informazioni sull'aggregazione, fare riferimento all'argomento Aggregazione. Questa nota descrive semplicemente il supporto dell'aggregazione incorporato nelle mappe dell'interfaccia e del framework.

Esistono due modi per utilizzare l'aggregazione: (1) tramite un oggetto COM che supporta l'aggregazione e (2) implementando un oggetto che può essere aggregato da un altro. Queste funzionalità possono essere indicate come "utilizzo di un oggetto di aggregazione" e "rendere un oggetto aggregabile". MFC supporta entrambi.

Utilizzo di un oggetto di aggregazione

Per utilizzare un oggetto di aggregazione, è necessario disporre di un modo per collegare l'aggregato nel meccanismo di QueryInterface. In altre parole, l'oggetto di aggregazione deve comportarsi come se fosse una parte nativa dell'oggetto. In che modo si collega al meccanismo di mappa dell'interfaccia di MFC? Oltre alla macro INTERFACE_PART, in cui un oggetto annidato viene mappato a un IID, è inoltre possibile dichiarare un oggetto di aggregazione come parte della classe derivata CCmdTarget. A tale scopo, si utilizza la macro INTERFACE_AGGREGATE. Ciò consente di specificare una variabile membro (che deve essere un puntatore a IUnknown o una classe derivata), che deve essere integrata nel meccanismo di mappa dell'interfaccia. Se il puntatore non ha valore NULL quando viene chiamata CCmdTarget::ExternalQueryInterface, il framework chiama automaticamente la funzione membro QueryInterface dell'oggetto aggregato, se l'IID richiesto non è uno degli IID nativi supportati dell'oggetto CCmdTarget.

Per utilizzare la macro INTERFACE_AGGREGATE

  1. Dichiarare una variabile membro (IUnknown*) che conterrà un puntatore all'oggetto di aggregazione.

  2. Includere una macro INTERFACE_AGGREGATE nella mappa dell'interfaccia, che fa riferimento alla variabile membro per nome.

  3. A un certo punto (in genere durante CCmdTarget::OnCreateAggregates), inizializzare la variabile membro su un valore diverso da NULL.

Di seguito è riportato un esempio.

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

La variabile m_lpAggrInner viene inizializzata nel costruttore a NULL. Il framework ignora una variabile membro NULL nell'implementazione predefinita di QueryInterface. OnCreateAggregates è una buona posizione per creare gli oggetti di aggregazione. Sarà necessario chiamarlo in modo esplicito se si crea l'oggetto all'esterno dell'implementazione MFC di COleObjectFactory. Il motivo per creare aggregati in CCmdTarget::OnCreateAggregates nonché l'utilizzo di CCmdTarget::GetControllingUnknown diventeranno evidenti quando si discuterà della creazione di oggetti aggregabili.

Questa tecnica dà all'oggetto tutte le interfacce supportate dall'oggetto aggregato oltre alle relative interfacce native. Se si desidera solo un sottoset delle interfacce che supportano l'aggregazione, è possibile eseguire l'override di CCmdTarget::GetInterfaceHook. Ciò consente un basso livello di possibilità di hook, in modo analogo a QueryInterface. In genere, sono richieste tutte le interfacce supportate dall'aggregazione.

Rendere un'implementazione dell'oggetto aggregabile

Affinché un oggetto sia aggregabile, l'implementazione di AddRef, Releasee QueryInterface devono delegare uno "unknown di controllo". In altre parole per far parte dell'oggetto, deve delegare AddRef, Releasee QueryInterface a un oggetto diverso, anch'esso derivato da IUnknown. Questo "unknown di controllo" viene fornito all'oggetto quando viene creato, ovvero viene fornito all'implementazione di COleObjectFactory. L'implementazione comporta una piccola quantità di sovraccarico e in alcuni casi non è desiderabile, quindi in MFC è reso facoltativo. Per consentire a un oggetto di essere aggregabile, chiamare CCmdTarget::EnableAggregation dal costruttore dell'oggetto.

Se l'oggetto utilizza anche aggregazioni, è inoltre necessario assicurarsi di passare il valore "unknown di controllo" corretto agli oggetti aggregati. In genere questo puntatore IUnknown viene passato all'oggetto quando si crea l'aggregazione. Ad esempio, il parametro pUnkOuter è l'"unknown di controllo"" per gli oggetti creati con CoCreateInstance. Il puntatore "unknown di controllo" corretto può essere recuperato chiamando CCmdTarget::GetControllingUnknown. Il valore restituito dalla funzione, tuttavia, non è valido per il costruttore. Per questo motivo, è consigliabile creare le aggregazioni solo in un override di CCmdTarget::OnCreateAggregates, dove il valore restituito da GetControllingUnknown è affidabile, anche se creato dall'implementazione COleObjectFactory.

È inoltre importante che l'oggetto modifichi il conteggio dei riferimenti corretto quando si aggiungono o si rilasciano i conteggi dei riferimenti artificiali. Per verificare che sia questo il caso, chiamare sempre ExternalAddRef e ExternalRelease anziché InternalRelease e InternalAddRef. È raro chiamare InternalRelease o InternalAddRef su una classe che supporta l'aggregazione.

Materiale di riferimento

L'utilizzo avanzato di OLE, come la definizione delle interfacce personalizzate o la sostituzione dell'implementazione del framework delle interfacce OLE richiede l'utilizzo del meccanismo della mappa dell'interfaccia sottostante.

In questa sezione vengono illustrate le macro e le API utilizzate per implementare queste funzionalità avanzate.

CCmdTarget::EnableAggregation - Descrizione della funzione

void EnableAggregation();

Osservazioni

Chiamare questa funzione nel costruttore della classe derivata se si desidera supportare l'aggregazione OLE per gli oggetti di questo tipo. Ciò prepara un'implementazione speciale di IUnknown necessaria per gli oggetti aggregabili.

CCmdTarget::ExternalQueryInterface - Descrizione di funzione

DWORD ExternalQueryInterface( 
   const void FAR* lpIID, 
   LPVOID FAR* ppvObj 
);

Osservazioni

Parametri

  • lpIID
    Puntatore far a un IID (il primo argomento a QueryInterface)

  • ppvObj
    Puntatore a un IUnknown* (secondo argomento a QueryInterface)

Osservazioni

Chiamare questa funzione nell'implementazione di IUnknown per ogni interfaccia implementata dalla classe. Questa funzione fornisce l'implementazione basata sui dati standard di QueryInterface in base alla mappa dell'interfaccia dell'oggetto. È necessario eseguire il cast del valore restituito in un HRESULT. Se l'oggetto è aggregato, questa funzione chiamerà "IUnknown di controllo" anziché utilizzare la mappa dell'interfaccia locale.

CCmdTarget::ExternalAddRef - Descrizione della funzione

DWORD ExternalAddRef();

Osservazioni

Chiamare questa funzione nell'implementazione di IUnknown::AddRef per ogni interfaccia implementata dalla classe. Il valore restituito è il nuovo conteggio dei riferimenti all'oggetto CCmdTarget. Se l'oggetto è aggregato, questa funzione chiamerà "IUnknown di controllo" anziché modificare il conteggio dei riferimenti locali.

CCmdTarget::ExternalRelease - Descrizione di funzione

DWORD ExternalRelease();

Osservazioni

Chiamare questa funzione nell'implementazione di IUnknown::Release per ogni interfaccia implementata dalla classe. Il valore restituito indica il nuovo conteggio dei riferimenti all'oggetto. Se l'oggetto è aggregato, questa funzione chiamerà "IUnknown di controllo" anziché modificare il conteggio dei riferimenti locali.

DECLARE_INTERFACE_MAP - Descrizione delle macro

DECLARE_INTERFACE_MAP

Osservazioni

Utilizzare questa macro in qualsiasi classe derivata da CCmdTarget che disporrà di una mappa dell'interfaccia. Utilizzato in modo analogo a DECLARE_MESSAGE_MAP. Questa chiamata della macro deve essere inserita nella definizione della classe, in genere un file di intestazione (con estensione H). Una classe con DECLARE_INTERFACE_MAP deve definire la mappa dell'interfaccia nel file di implementazione (CPP) con le macro END_INTERFACE_MAP e BEGIN_INTERFACE_MAP.

BEGIN_INTERFACE_PART e END_INTERFACE_PART - Descrizioni delle macro

BEGIN_INTERFACE_PART( 
   localClass,
   iface 
);
END_INTERFACE_PART( 
   localClass 
)

Osservazioni

Parametri

  • ocalClass
    Nome della classe che implementa l'interfaccia

  • iface
    Nome dell'interfaccia che questa classe implementa

Osservazioni

Per ogni interfaccia che la classe implementerà, è necessario disporre di una coppia END_INTERFACE_PART e BEGIN_INTERFACE_PART. Queste macro definiscono una classe locale derivata dall'interfaccia OLE che viene anche definite come variabile membro incorporata di tale classe. I membri AddRef, Release e QueryInterface sono dichiarati automaticamente. È necessario includere le dichiarazioni per le altre funzioni membro che fanno parte dell'interfaccia implementata (tali dichiarazioni si trovano tra le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART).

L'argomento iface è l'interfaccia OLE che si desidera implementare, come IAdviseSink, IPersistStorage o la propria interfaccia personalizzata.

L'argomento localClass è il nome della classe locale che verrà definita. Una "X" verrà automaticamente anteposta al nome. Questa convenzione di denominazione viene utilizzata per evitare conflitti con le classi globali che hanno lo stesso nome. Inoltre, il nome del membro incorporato, uguale al nome localClass a meno che non sia preceduto da "m_x".

Di seguito è riportato un esempio.

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
   STDMETHOD_(void,OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
   STDMETHOD_(void,OnViewChange)(DWORD, LONG);
   STDMETHOD_(void,OnRename)(LPMONIKER);
   STDMETHOD_(void,OnSave)();
   STDMETHOD_(void,OnClose)();
END_INTERFACE_PART(MyAdviseSink)

definirebbe una classe locale denominata XMyAdviseSink derivata da IAdviseSink e un membro della classe in cui è dichiarato denominato m_xMyAdviseSink. Nota:

Nota

Le righe che iniziano con STDMETHOD_ sono essenzialmente copiate da OLE2.H e leggermente modificate.La copia di questi oggetti da OLE2.H può ridurre gli errori difficili da risolvere.

BEGIN_INTERFACE_MAP e END_INTERFACE_MAP - Descrizioni delle macro

BEGIN_INTERFACE_MAP( 
   theClass,
   baseClass 
) 
END_INTERFACE_MAP

Osservazioni

Parametri

  • theClass
    Classe in cui la mappa dell'interfaccia deve essere definita

  • baseClass
    Classe da cui deriva theClass.

Osservazioni

Le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP sono utilizzate nel file di implementazione per definire la mappa dell'interfaccia. Per ogni interfaccia implementata sono presenti uno o più chiamate di macro INTERFACE_PART. Per ogni aggregazione utilizzata dalla classe, esiste una chiamata della macro INTERFACE_AGGREGATE.

INTERFACE_PART — Descrizione delle macro

INTERFACE_PART( 
   theClass,
   iid, 
   localClass 
)

Osservazioni

Parametri

  • theClass
    Nome della classe che contiene la mappa dell'interfaccia.

  • iid
    IID di cui deve essere eseguito il mapping alla classe incorporata.

  • localClass
    Nome della classe locale (meno la 'X).

Osservazioni

Questa macro viene utilizzata tra la macro BEGIN_INTERFACE_MAP e la macro END_INTERFACE_MAP per ogni interfaccia supportata dall'oggetto. Consente di eseguire il mapping di un IID a un membro della classe indicata da theClass e localClass. "m_x" sarà aggiunto all'oggetto localClass automaticamente. Si noti che più di un IID può essere associato a un singolo membro. È molto utile quando si implementa solo un'interfaccia in gran parte derivata e si intende fornire anche tutte le interfacce intermedie. Un esempio efficace è l'interfaccia IOleInPlaceFrameWindow. La gerarchia ha il seguente aspetto:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Se un oggetto implementa IOleInPlaceFrameWindow, un client può chiamare QueryInterface su una qualsiasi di queste interfacce: IOleUIWindow, IOleWindow o IUnknown, oltre all'interfaccia "più derivata" IOleInPlaceFrameWindow (quella che si sta effettivamente implementando). Per gestire la situazione, è possibile utilizzare più macro INTERFACE_PART per eseguire il mapping di ogni interfaccia di base all'interfaccia IOleInPlaceFrameWindow :

nel file di definizione di classe:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

nel file di implementazione di classe:

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

IUnknown è sempre richiesto nel framework.

INTERFACE_PART — Descrizione delle macro

INTERFACE_AGGREGATE( 
   theClass,
   theAggr 
)

Osservazioni

Parametri

  • theClass
    Nome della classe che contiene la mappa dell'interfaccia,

  • theAggr
    Nome della variabile membro che deve essere aggregata.

Osservazioni

Questa macro viene utilizzata per indicare al framework che la classe utilizza un oggetto di aggregazione. Deve essere visualizzato tra BEGIN_INTERFACE_PART' e END_INTERFACE_PART'. Un oggetto di aggregazione è un oggetto separato, derivato da IUnknown. Utilizzando un'aggregazione e la macro INTERFACE_AGGREGATE, è possibile far sì che vengano visualizzate tutte le interfacce supportate dall'aggregazione perché vengano direttamente supportate dall'oggetto. L'argomento theAggr è semplicemente il nome di una variabile membro della classe derivata da IUnknown (direttamente o indirettamente). Tutte le macro INTERFACE_AGGREGATE, una volta inserite in una mappa dell'interfaccia, devono seguire le macro INTERFACE_PART.

Vedere anche

Altre risorse

Note tecniche per numero

Note tecniche per categoria