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.
Alla base di OLE 2 vi è "Component Object Model OLE" (COM OLE). COM definisce uno standard correlato al modo in cui comunicano tra loro gli oggetti che cooperano. Lo standard include i dettagli sull'aspetto di un "oggetto", tra cui il modo in cui i metodi vengono inviati in un oggetto. COM definisce anche una classe base, da cui vengono derivate tutte le classi compatibili con COM. Questa classe di base è IUnknown. Anche se l'interfaccia IUnknown è definita classe C++, COM non è specifica di un linguaggio, ma può essere implementata 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. ma definisce semplicemente il protocollo attraverso il quale comunicano gli oggetti e non i dettagli riguardo alle operazioni eseguite dalle implementazioni. Si tratta di un comportamento ragionevole per un sistema che permette la massima flessibilità. È compito di MFC implementare un comportamento predefinito per i programmi MFC/C++.
Per comprendere l'implementazione di MFC di IUnknown , è necessario prima comprendere qual è questa interfaccia. Di seguito è definita una versione semplificata di IUnknown :
class IUnknown
{
public:
virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
Nota
Nella figura sono stati esclusi determinati dettagli sulle convenzioni di chiamata necessarie, come __stdcall
.
Le funzioni membro AddRef e Release controllano la gestione della memoria dell'oggetto. COM usa uno schema di conteggio dei riferimenti per tenere traccia degli oggetti. A un oggetto non viene mai fatto riferimento direttamente come avviene in C++. Invece, agli oggetti COM viene sempre fatto riferimento tramite un puntatore. Per rilasciare l'oggetto al termine dell'utilizzo da parte del proprietario, viene chiamato il membro Release dell'oggetto (anziché usare l'eliminazione dell'operatore, come avviene per un oggetto C++ tradizionale). Il meccanismo di conteggio dei riferimenti permette la gestione di più riferimenti a un singolo oggetto. Un'implementazione di AddRef e Release mantiene un conteggio dei riferimenti sull'oggetto , ovvero l'oggetto non viene eliminato fino a quando il conteggio dei riferimenti non raggiunge lo zero.
AddRef e Release sono piuttosto semplici dal punto di vista dell'implementazione. Ecco un'implementazione piuttosto banale:
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 è molto interessante avere un oggetto le cui uniche funzioni membro sono AddRef e Release. Sarebbe bello indicare all'oggetto di eseguire più operazioni rispetto a quelle fornite da IUnknown. In questo caso QueryInterface è utile. perché permette di ottenere un'"interfaccia" diversa nello stesso oggetto. Queste interfacce sono in genere derivate da IUnknown e aggiungono funzionalità aggiuntive aggiungendo nuove funzioni membro. Le interfacce COM non hanno mai variabili membro dichiarate nell'interfaccia e tutte le funzioni membro vengono dichiarate come virtuali pure. Ad 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 usando IID
di IPrintInterface
. IID
è un numero a 128 bit che identifica in modo univoco l'interfaccia. Esiste un oggetto IID
per ogni interfaccia definita dall'utente o da OLE. Se pUnk è un puntatore a un oggetto IUnknown , il codice da cui recuperare un oggetto 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 implementare un oggetto che supporta sia l'interfaccia IPrintInterface che IUnknown In questo caso è semplice perché IPrintInterface è derivato direttamente da IUnknown , implementando IPrintInterface, IUnknown è supportato automaticamente. Ad 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 le stesse di quelle implementate in precedenza. CPrintObj::QueryInterface
sarà 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, viene restituito un puntatore all'oggetto, altrimenti si verifica un errore. Si noti anche che un oggetto QueryInterface con esito positivo restituisce un elemento AddRef implicito. Naturalmente, sarebbe necessario implementare anche CEditObj::Print. Questo è semplice perché IPrintInterface è stato derivato direttamente dall'interfaccia IUnknown . Tuttavia, se si vogliono supportare due interfacce diverse, entrambe derivate da IUnknown, considerare quanto segue:
class IEditInterface : public IUnkown
{
public:
virtual void EditObject() = 0;
};
Benché esistano diversi modi per implementare una classe supportando IEditInterface e IPrintInterface, tra cui l'uso dell'ereditarietà multipla di C++, questa nota è incentrata sull'uso di classi annidate per implementare la 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;
};
Ecco 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 IUnknown viene inserita nella classe CEditPrintObj anziché duplicare il codice in CEditPrintObj::CEditObj e CEditPrintObj::CPrintObj. In questo modo, si riduce la quantità di codice e si evitano bug. Il punto chiave qui è che dall'interfaccia IUnknown è possibile chiamare QueryInterface per recuperare qualsiasi interfaccia che l'oggetto potrebbe supportare e da ognuna di queste interfacce è possibile eseguire la stessa operazione. Ciò significa che tutte le funzioni QueryInterface disponibili in ogni interfaccia devono comportarsi esattamente allo stesso modo. Perché questi oggetti incorporati possano chiamare l'implementazione nell'"oggetto esterno", viene usato un puntatore all'indietro (m_pParent). Il puntatore m_pParent viene inizializzato durante il costruttore CEditPrintObj. È quindi necessario implementare anche CEditPrintObj::CPrintObj::PrintObject e CEditPrintObj::CEditObj::EditObject. È stata inserita una certa quantità di codice per aggiungere una sola funzionalità, ovvero la possibilità di modificare l'oggetto. Fortunatamente, è piuttosto insolito (anche se può succedere) che le interfacce includano una sola funzione membro e, in questo caso, EditObject e PrintObject vengono in genere combinati in una singola interfaccia.
Per uno scenario così semplice, la quantità di codice e le spiegazioni necessarie sono quasi eccessive. Le classi MFC/OLE offrono un'alternativa più facile. L'implementazione MFC usa una tecnica simile al modo in cui viene eseguito il wrapping dei messaggi di Windows con le mappe messaggi. Questa funzionalità è denominata Interface Mappe ed è descritta nella sezione successiva.
Mappe dell'interfaccia MFC
MFC/OLE include un'implementazione di "mappe dell'interfaccia" simile alle "mappe messaggi" e alle "mappe di invio" di MFC quanto a concetto ed esecuzione. Le funzionalità di base delle mappe dell'interfaccia di MFC sono le seguenti:
Implementazione standard di IUnknown, incorporata nella
CCmdTarget
classe .Manutenzione del conteggio dei riferimenti, modificato da AddRef e Release
Implementazione guidata dai dati di QueryInterface
Le mappe dell'interfaccia supportano anche queste funzionalità avanzate:
Supporto per la creazione di oggetti COM aggregabili
Supporto per l'uso di oggetti aggregati nell'implementazione di un oggetto COM
L'implementazione è associabile ed estendibile
Per altre informazioni sull'aggregazione, vedere l'argomento Aggregazione .
Il supporto delle mappe dell'interfaccia di MFC è radicato nella classe CCmdTarget
. CCmdTarget
Conteggio dei riferimenti "has-a", nonché tutte le funzioni membro associate all'implementazione IUnknown (il conteggio dei riferimenti, ad esempio, è in CCmdTarget
). Per creare una classe che supporta OLE COM, è necessario derivare una classe da CCmdTarget
e usare diverse macro e funzioni membro di CCmdTarget
per implementare le interfacce desiderate. L'implementazione di MFC usa classi annidate per definire l'implementazione di ogni interfaccia in modo molto simile all'esempio precedente. Questa operazione viene resa più semplice con un'implementazione standard di IUnknown e con alcune macro che eliminano parte del codice ripetitivo.
Nozioni di base sulle mappe dell'interfaccia
Per implementare una classe usando le mappe dell'interfaccia di MFC
Derivare una classe direttamente o indirettamente da
CCmdTarget
.Usare la funzione
DECLARE_INTERFACE_MAP
nella definizione della classe derivata.Per ogni interfaccia che si desidera supportare, usare le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART nella definizione della classe.
Nel file di implementazione usare le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP per definire la mappa dell'interfaccia della classe.
Per ogni IID supportato, usare la macro INTERFACE_PART tra le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP per eseguire il mapping dell'IID a una specifica "parte" della classe.
Implementare ognuna delle classi annidate che rappresentano le interfacce supportate.
Utilizzare la macro METHOD_PROLOGUE per accedere all'oggetto padre derivato
CCmdTarget
da .AddRef, Release e QueryInterface può delegare all'implementazione
CCmdTarget
di queste funzioni (ExternalAddRef
,ExternalRelease
eExternalQueryInterface
).
L'esempio di CPrintEditObj precedente potrebbe essere implementato in questo modo:
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 dichiarazione precedente crea una classe derivata da CCmdTarget
. La macro DECLARE_INTERFACE_MAP indica al framework che questa classe avrà una mappa dell'interfaccia personalizzata. Inoltre, le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART definiscono classi annidate, in questo caso con nomi CEditObj e CPrintObj (la X viene usata solo per distinguere le classi annidate dalle classi globali che iniziano con "C" e classi di interfaccia che iniziano con "I"). Vengono creati due membri annidati di queste classi, rispettivamente m_CEditObj e m_CPrintObj. Le macro dichiarano automaticamente le funzioni AddRef, Release e QueryInterface , pertanto si dichiarano solo le funzioni specifiche di questa interfaccia: EditObject e PrintObject (la macro OLE STDMETHOD viene usata in modo che le parole chiave _stdcall e virtuali siano fornite come appropriato per la piattaforma di destinazione).
Per implementare la mappa dell'interfaccia per la classe:
BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()
In questo modo, si connette rispettivamente l'IID IID_IPrintInterface a m_CPrintObj e IID_IEditInterface a m_CEditObj. L'implementazione CCmdTarget
di QueryInterface (CCmdTarget::ExternalQueryInterface
) usa questa mappa per restituire puntatori a m_CPrintObj e m_CEditObj quando richiesto. Non è necessario includere una voce per IID_IUnknown
. Il framework userà la prima interfaccia nella mappa, in questo caso m_CPrintObj, quando viene richiesto IID_IUnknown
.
Anche se la macro BEGIN_INTERFACE_PART ha dichiarato automaticamente le funzioni AddRef, Release e QueryInterface , è comunque 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 sarà simile alle definizioni indicate sopra per CEditPrintObj::CEditObj. Benché sia possibile creare una macro da usare per generare automaticamente queste funzioni (nello sviluppo MFC/OLE in passato avveniva così), diventa difficile impostare punti di interruzione quando una macro genera più di una riga di codice. Per questo motivo, questo codice viene espanso manualmente.
Se si usa l'implementazione del framework delle mappe messaggi, è necessario eseguire alcune operazioni:
Implementare QueryInterface
Implementare AddRef e Release
Dichiarare uno di questi metodi predefiniti in entrambe le interfacce
Inoltre, il framework usa le mappe messaggi internamente. In questo modo, è possibile derivare da una classe del framework, ad esempio COleServerDoc
, che supporta già certe interfacce e fornisce sostituzioni o aggiunte per le interfacce specificate dal framework. Questo è possibile perché il 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 di base.
Nota
In genere, non è possibile riusare le implementazioni predefinite di MFC delle interfacce OLE semplicemente ereditando la specializzazione incorporata di tale interfaccia dalla versione MFC. Ciò non è possibile perché l'uso della macro METHOD_PROLOGUE per ottenere l'accesso all'oggetto derivato da contenitore CCmdTarget
implica un offset fisso dell'oggetto incorporato dall'oggetto CCmdTarget
derivato da . Di conseguenza, ad esempio, non è possibile derivare un oggetto XMyAdviseSink incorporato dall'implementazione di MFC in COleClientItem::XAdviseSink
, perché XAdviseSink presuppone di trovarsi in corrispondenza di un offset specifico dall'inizio dell'oggetto COleClientItem
.
Nota
Tuttavia, è possibile delegare all'implementazione MFC per tutte le funzioni che si vuole definire come comportamento predefinito di MFC. A questo scopo, viene usata l'implementazione MFC di IOleInPlaceFrame
(XOleInPlaceFrame) nella classe COleFrameHook
(con delega a m_xOleInPlaceUIWindow per molte funzioni). Questa progettazione è stata scelta per ridurre le dimensioni di runtime degli oggetti che implementano molte interfacce ed elimina la necessità di un puntatore all'indietro (ad esempio, nel modo in cui è stato usato m_pParent nella sezione precedente).
Aggregazione e mappe dell'interfaccia
Oltre a supportare oggetti COM autonomi, MFC supporta anche l'aggregazione. L'aggregazione stessa è troppo complessa da discutere qui; Per altre informazioni sull'aggregazione, vedere l'argomento Aggregazione . Questa nota descriverà semplicemente il supporto per l'aggregazione integrata nel framework e nelle mappe dell'interfaccia.
Esistono due modi per usare l'aggregazione: (1) usando un oggetto COM che supporta l'aggregazione e (2) implementando un oggetto che può essere aggregato da un altro. Queste funzionalità possono essere definite "uso di un oggetto aggregato" e "impostazione di un oggetto come aggregabile". MFC le supporta entrambe.
Uso di un oggetto aggregato
Per usare un oggetto aggregato, è necessario determinare come associare l'aggregato al meccanismo di QueryInterface. In altre parole, l'oggetto aggregato deve comportarsi come se fosse una parte nativa dell'oggetto. In che modo questa associazione viene eseguita nel meccanismo di mapping dell'interfaccia di MFC Oltre alla macro INTERFACE_PART, in cui viene eseguito il mapping di un oggetto annidato a un IID, è anche possibile dichiarare un oggetto aggregato come parte della CCmdTarget
classe derivata. A tale scopo, viene utilizzata la macro INTERFACE_AGGREGATE. In questo modo è possibile specificare una variabile membro (che deve essere un puntatore a una classe IUnknown o derivata), che deve essere integrata nel meccanismo di mapping dell'interfaccia. Se il puntatore non è NULL quando CCmdTarget::ExternalQueryInterface
viene chiamato, il framework chiamerà automaticamente la funzione membro QueryInterface dell'oggetto di aggregazione, se l'oggetto IID
richiesto non è uno dei nativi IID
supportati dall'oggetto CCmdTarget
stesso.
Per usare la macro INTERFACE_AGGREGATE
Dichiarare una variabile membro (
IUnknown*
) che conterrà un puntatore all'oggetto aggregato.Includere una macro INTERFACE_AGGREGATE nella mappa dell'interfaccia, che fa riferimento alla variabile membro in base al nome.
A un certo punto (in genere durante
CCmdTarget::OnCreateAggregates
), inizializzare la variabile membro in un oggetto diverso da NULL.
Ad 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 in NULL. Il framework ignora una variabile membro NULL nell'implementazione predefinita di QueryInterface. OnCreateAggregates
è un buon punto in cui creare gli oggetti aggregati. Sarà necessario chiamarlo in modo esplicito se l'oggetto viene creato all'esterno dell'implementazione MFC di COleObjectFactory
. Il motivo per cui creare aggregati in CCmdTarget::OnCreateAggregates
e quello per cui utilizzare CCmdTarget::GetControllingUnknown
risulteranno evidenti quando verrà descritta la creazione di oggetti aggregabili.
Questa tecnica fornisce agli oggetti tutte le interfacce supportate dall'oggetto aggregato, insieme alle interfacce native. Se si vuole solo un subset delle interfacce supportate dall'aggregato, è possibile ignorare CCmdTarget::GetInterfaceHook
. Ciò consente un hookability di basso livello, simile a QueryInterface. In genere, è preferibile ottenere tutte le interfacce supportate dall'aggregato.
Impostazione dell'implementazione di un oggetto come aggregabile
Affinché un oggetto sia aggregabile, l'implementazione di AddRef, Release e QueryInterface deve delegare a un "controllo sconosciuto". In altre parole, affinché faccia parte dell'oggetto, deve delegare AddRef, Release e QueryInterface a un oggetto diverso, derivato anche da IUnknown. Questo puntatore "controlling unknown" viene fornito all'oggetto quando questo viene creato, ovvero viene fornito all'implementazione di COleObjectFactory
. Questa implementazione implica una quantità ridotta di overhead. Poiché questo potrebbe essere un problema, MFC la rende facoltativa. Per fare in modo che un oggetto sia aggregabile, chiamare CCmdTarget::EnableAggregation
dal costruttore dell'oggetto.
Se l'oggetto usa anche aggregati, è necessario assicurarsi di passare il puntatore "controlling unknown" corretto agli oggetti aggregati. In genere questo puntatore IUnknown viene passato all'oggetto quando viene creata l'aggregazione. Ad esempio, il parametro pUnkOuter è il puntatore "controlling unknown" per gli oggetti creati con CoCreateInstance
. Il puntatore "controlling unknown" corretto può essere recuperato chiamando CCmdTarget::GetControllingUnknown
. Il valore restituito dalla funzione, tuttavia, non è valido durante il costruttore. Per questo motivo, è consigliabile creare gli aggregati solo in un override di CCmdTarget::OnCreateAggregates
, in cui il valore restituito da GetControllingUnknown
è affidabile, anche se creato dall'implementazione di COleObjectFactory
.
È anche importante che l'oggetto modifichi il conteggio dei riferimenti corretto durante l'aggiunta o il rilascio di conteggi dei riferimenti artificiali. Per assicurarsi che sia questo il caso, chiamare sempre ExternalAddRef
e ExternalRelease
invece di InternalRelease
e InternalAddRef
. È raro chiamare InternalRelease
o InternalAddRef
in una classe che supporta l'aggregazione.
Materiale di riferimento
L'utilizzo avanzato di OLE, ad esempio per la definizione di interfacce personalizzate o l'override dell'implementazione del framework delle interfacce OLE, richiede l'uso del meccanismo della mappa dell'interfaccia sottostante.
Questa sezione descrive ogni macro e le API usate per implementare queste funzionalità avanzate.
CCmdTarget::EnableAggregation: descrizione della funzione
void EnableAggregation();
Osservazioni:
Chiamare questa funzione nel costruttore della classe derivata se si vuole supportare l'aggregazione OLE per oggetti di questo tipo. Questa funzione prepara una speciale implementazione di IUnknown necessaria per gli oggetti aggregabili.
CCmdTarget::ExternalQueryInterface: descrizione della funzione
DWORD ExternalQueryInterface(
const void FAR* lpIID,
LPVOIDFAR* ppvObj
);
Parametri
lpIID
Puntatore far a un IID (primo argomento per QueryInterface)
ppvObj
Puntatore a IUnknown* (secondo argomento per 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 HRESULT. Se l'oggetto è aggregato, questa funzione chiamerà l'interfaccia "IUnknown di controllo" invece di usare 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 nell'oggetto CCmdTarget. Se l'oggetto è aggregato, questa funzione chiama l'interfaccia "IUnknown di controllo" invece di modificare il conteggio dei riferimenti locale.
CCmdTarget::ExternalRelease: descrizione della 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 nell'oggetto. Se l'oggetto è aggregato, questa funzione chiama l'interfaccia "IUnknown di controllo" invece di modificare il conteggio dei riferimenti locale.
DECLARE_INTERFACE_MAP: descrizione della macro
DECLARE_INTERFACE_MAP
Osservazioni:
Usare questa macro in qualsiasi classe derivata da CCmdTarget
che avrà una mappa dell'interfaccia. Usato in modo analogo a DECLARE_MESSAGE_MAP. La chiamata di questa macro deve essere inserita nella definizione della classe, in genere nel 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 BEGIN_INTERFACE_MAP e END_INTERFACE_MAP.
BEGIN_INTERFACE_PART e END_INTERFACE_PART: descrizioni delle macro
BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)
Parametri
localClass
Nome della classe che implementa l'interfaccia
iface
Nome dell'interfaccia implementata dalla classe
Osservazioni:
Per ogni interfaccia che verrà implementata dalla classe, è necessario avere una coppia BEGIN_INTERFACE_PART e END_INTERFACE_PART. Queste macro definiscono una classe locale derivata dall'interfaccia OLE specificata, nonché una variabile membro incorporata della classe. I membri AddRef, Release e QueryInterface vengono dichiarati automaticamente. È necessario includere le dichiarazioni per le altre funzioni membro che fanno parte dell'interfaccia implementata (tali dichiarazioni vengono posizionate tra le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART).
L'argomento iface è l'interfaccia OLE che si vuole implementare, ad esempio IAdviseSink
, o IPersistStorage
(o la propria interfaccia personalizzata).
L'argomento localClass è il nome della classe locale che verrà definita. Al nome verrà anteposta automaticamente una "X". Questa convenzione di denominazione viene usata per evitare conflitti con le classi globali che hanno lo stesso nome. Inoltre, il nome del membro incorporato, uguale al nome localClass , ad eccezione del prefisso "m_x".
Ad 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)
definisce una classe locale chiamata XMyAdviseSink e derivata da IAdviseSink e un membro della classe in cui questa viene dichiarata, chiamato m_xMyAdviseSink.
Nota
Le righe che iniziano con STDMETHOD
_ vengono essenzialmente copiate da OLE2. H e modificato leggermente. La copia di queste righe 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
Parametri
theClass
Classe in cui deve essere definita la mappa dell'interfaccia
Baseclass
Classe da cui deriva la classe .
Osservazioni:
Le macro BEGIN_INTERFACE_MAP e END_INTERFACE_MAP vengono utilizzate nel file di implementazione per definire effettivamente la mappa dell'interfaccia. Per ogni interfaccia implementata è presente una o più chiamate di macro INTERFACE_PART. Per ogni aggregazione usata dalla classe è presente una chiamata di macro INTERFACE_AGGREGATE.
INTERFACE_PART: descrizione della macro
INTERFACE_PART(theClass, iid, localClass)
Parametri
theClass
Nome della classe che contiene la mappa dell'interfaccia.
Iid
IID
di cui eseguire 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. Il 'm_x' verrà aggiunto automaticamente alla classe local. Si noti che a un singolo membro possono essere associati più IID
. Questo comportamento è molto utile quando si implementa solo un'interfaccia "più derivata" e si vuole fornire anche tutte le interfacce intermedie. Un esempio valido è l'interfaccia IOleInPlaceFrameWindow
. La gerarchia ha questo aspetto:
IUnknown
IOleWindow
IOleUIWindow
IOleInPlaceFrameWindow
Se un oggetto implementa IOleInPlaceFrameWindow
, un client può QueryInterface
usare una di queste interfacce: IOleUIWindow
, IOleWindow
o IUnknown, oltre all'interfaccia IOleInPlaceFrameWindow
"più derivata" (quella effettivamente implementata). Per gestire questa operazione, è possibile usare più di una macro INTERFACE_PART per eseguire il mapping di ogni interfaccia di base all'interfaccia IOleInPlaceFrameWindow
:
Nel file di definizione della classe:
BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)
Nel file di implementazione della 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
Il framework si occupa di IUnknown perché è sempre obbligatorio.
INTERFACE_PART: descrizione della macro
INTERFACE_AGGREGATE(theClass, theAggr)
Parametri
theClass
Nome della classe che contiene la mappa dell'interfaccia.
theAggr
Nome della variabile membro che deve essere aggregata.
Osservazioni:
Questa macro viene usata per indicare al framework che la classe usa un oggetto aggregato. Deve essere visualizzata tra le macro BEGIN_INTERFACE_PART e END_INTERFACE_PART. Un oggetto aggregato è un oggetto separato, derivato da IUnknown. Utilizzando un'aggregazione e la macro INTERFACE_AGGREGATE, è possibile rendere tutte le interfacce supportate dall'aggregazione sembrano essere supportate direttamente dall'oggetto . L'argomentoAggr è semplicemente il nome di una variabile membro della classe derivata da IUnknown (direttamente o indirettamente). Tutte le macro INTERFACE_AGGREGATE devono seguire le macro INTERFACE_PART quando vengono posizionate in una mappa dell'interfaccia.