TN039: implementazione di automazione 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.
Panoramica dell'interfaccia OLE IDispatch
L'interfaccia IDispatch
è il mezzo con cui le applicazioni espongono metodi e proprietà in modo che altre applicazioni, ad esempio Visual BASIC o altri linguaggi, possano usare le funzionalità dell'applicazione. La parte più importante di questa interfaccia è la IDispatch::Invoke
funzione . MFC usa le "mappe dispatch" per implementare IDispatch::Invoke
. La mappa dispatch fornisce le informazioni sull'implementazione MFC sul layout o sulla "forma" delle classi derivate da , in modo che possa modificare direttamente le proprietà dell'oggetto o chiamare funzioni membro all'interno dell'oggetto CCmdTarget
per soddisfare IDispatch::Invoke
le richieste.
Per la maggior parte, ClassWizard e MFC collaborano per nascondere la maggior parte dei dettagli dell'automazione OLE dal programmatore dell'applicazione. Il programmatore si concentra sulle funzionalità effettive da esporre nell'applicazione e non deve preoccuparsi del plumbing sottostante.
Ci sono casi, tuttavia, in cui è necessario capire cosa sta facendo MFC dietro le quinte. Questa nota illustra il modo in cui il framework assegna DISPIDalle funzioni membro e alle proprietà. La conoscenza dell'algoritmo usato da MFC per l'assegnazione di DISPIDè necessaria solo quando è necessario conoscere gli ID, ad esempio quando si crea una "libreria dei tipi" per gli oggetti dell'applicazione.
Assegnazione DISPID MFC
Anche se l'utente finale dell'automazione (un utente di Visual Basic, ad esempio), vede i nomi effettivi delle proprietà e dei metodi abilitati per l'automazione nel codice (ad esempio obj. ShowWindow), l'implementazione di IDispatch::Invoke
non riceve i nomi effettivi. Per motivi di ottimizzazione, riceve un DISPID, ovvero un "cookie magic" a 32 bit che descrive il metodo o la proprietà a cui accedere. Questi valori DISPID vengono restituiti dall'implementazione IDispatch
tramite un altro metodo, denominato IDispatch::GetIDsOfNames
. Un'applicazione client di automazione chiamerà GetIDsOfNames
una volta per ogni membro o proprietà a cui intende accedere e le memorizza nella cache per le chiamate successive a IDispatch::Invoke
. In questo modo, la ricerca di stringhe dispendiosa viene eseguita una sola volta per ogni oggetto, anziché una volta per IDispatch::Invoke
ogni chiamata.
MFC determina gli oggetti DISPIDper ogni metodo e proprietà in base a due elementi:
Distanza dalla parte superiore della mappa di distribuzione (1 relativo)
Distanza della mappa di distribuzione dalla classe più derivata (0 relativa)
Il DISPID è suddiviso in due parti. LoWORD del DISPID contiene il primo componente, la distanza dalla parte superiore della mappa di distribuzione. HIWORD contiene la distanza dalla classe più derivata. Ad esempio:
class CDispPoint : public CCmdTarget
{
public:
short m_x, m_y;
// ...
DECLARE_DISPATCH_MAP()
// ...
};
class CDisp3DPoint : public CDispPoint
{
public:
short m_z;
// ...
DECLARE_DISPATCH_MAP()
// ...
};
BEGIN_DISPATCH_MAP(CDispPoint, CCmdTarget)
DISP_PROPERTY(CDispPoint, "x", m_x, VT_I2)
DISP_PROPERTY(CDispPoint, "y", m_y, VT_I2)
END_DISPATCH_MAP()
BEGIN_DISPATCH_MAP(CDisp3DPoint, CDispPoint)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
END_DISPATCH_MAP()
Come si può notare, sono disponibili due classi, entrambe le quali espongono interfacce di automazione OLE. Una di queste classi è derivata dall'altra e quindi sfrutta la funzionalità della classe base, inclusa la parte di automazione OLE ("x" e "y" in questo caso).
MFC genererà DISPIDs per la classe CDispPoint come indicato di seguito:
property X (DISPID)0x00000001
property Y (DISPID)0x00000002
Poiché le proprietà non si trovano in una classe di base, l'HIWORD del DISPID è sempre zero (la distanza dalla classe più derivata per CDispPoint è zero).
MFC genererà DISPIDs per la classe CDisp3DPoint come indicato di seguito:
property Z (DISPID)0x00000001
property X (DISPID)0x00010001
property Y (DISPID)0x00010002
Alla proprietà Z viene assegnato un DISPID con zero HIWORD poiché è definito nella classe che espone le proprietà CDisp3DPoint. Poiché le proprietà X e Y sono definite in una classe di base, l'HIWORD del DISPID è 1, poiché la classe in cui vengono definite queste proprietà è a una distanza di una derivazione dalla classe più derivata.
Nota
LoWORD è sempre determinato dalla posizione nella mappa, anche se sono presenti voci nella mappa con DISPID esplicito (vedere la sezione successiva per informazioni sulle versioni _ID delle DISP_PROPERTY
macro e DISP_FUNCTION
).
Funzionalità avanzate della mappa dispatch MFC
Esistono diverse funzionalità aggiuntive che ClassWizard non supporta con questa versione di Visual C++. ClassWizard supporta DISP_FUNCTION
, DISP_PROPERTY
e DISP_PROPERTY_EX
che definiscono rispettivamente un metodo, una proprietà della variabile membro e una proprietà della funzione membro get/set. Queste funzionalità sono in genere necessarie per creare la maggior parte dei server di automazione.
Le macro aggiuntive seguenti possono essere usate quando le macro supportate da ClassWizard non sono adeguate: DISP_PROPERTY_NOTIFY
e DISP_PROPERTY_PARAM
.
DISP_PROPERTY_NOTIFY — Descrizione macro
DISP_PROPERTY_NOTIFY(
theClass,
pszName,
memberName,
pfnAfterSet,
vtPropType)
Parametri
theClass
Nome della classe.
pszName
Nome esterno della proprietà.
Membername
Nome della variabile membro in cui è archiviata la proprietà.
pfnAfterSet
Nome della funzione membro da chiamare quando viene modificata la proprietà.
vtPropType
Valore che specifica il tipo della proprietà.
Osservazioni:
Questa macro è molto simile a DISP_PROPERTY, ad eccezione del fatto che accetta un argomento aggiuntivo. L'argomento aggiuntivo pfnAfterSet deve essere una funzione membro che non restituisce nulla e non accetta parametri, 'void OnPropertyNotify()'. Verrà chiamato dopo la modifica della variabile membro.
DISP_PROPERTY_PARAM — Descrizione macro
DISP_PROPERTY_PARAM(
theClass,
pszName,
pfnGet,
pfnSet,
vtPropType,
vtsParams)
Parametri
theClass
Nome della classe.
pszName
Nome esterno della proprietà.
memberGet
Nome della funzione membro utilizzata per ottenere la proprietà .
memberSet
Nome della funzione membro utilizzata per impostare la proprietà.
vtPropType
Valore che specifica il tipo della proprietà.
vtsParams
Stringa di spazio separata VTS_ per ogni parametro.
Osservazioni:
Analogamente alla macro DISP_PROPERTY_EX, questa macro definisce una proprietà a cui si accede con funzioni membro Get e Set separate. Questa macro, tuttavia, consente di specificare un elenco di parametri per la proprietà . Ciò è utile per implementare proprietà indicizzate o con parametri in un altro modo. I parametri verranno sempre inseriti per primi, seguiti dal nuovo valore per la proprietà . Ad esempio:
DISP_PROPERTY_PARAM(CMyObject, "item", GetItem, SetItem, VT_DISPATCH, VTS_I2 VTS_I2)
corrisponde alle funzioni membro get e set:
LPDISPATCH CMyObject::GetItem(short row, short col)
void CMyObject::SetItem(short row, short col, LPDISPATCH newValue)
DISP_XXXX_ID — Descrizioni delle macro
DISP_FUNCTION_ID(
theClass,
pszName,
dispid,
pfnMember,
vtRetVal,
vtsParams)
DISP_PROPERTY_ID(
theClass,
pszName,
dispid,
memberName,
vtPropType)
DISP_PROPERTY_NOTIFY_ID(
theClass,
pszName,
dispid,
memberName,
pfnAfterSet,
vtPropType)
DISP_PROPERTY_EX_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType)
DISP_PROPERTY_PARAM_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType,
vtsParams)
Parametri
theClass
Nome della classe.
pszName
Nome esterno della proprietà.
Dispid
DISPID fisso per la proprietà o il metodo .
pfnGet
Nome della funzione membro utilizzata per ottenere la proprietà .
pfnSet
Nome della funzione membro utilizzata per impostare la proprietà.
Membername
Nome della variabile membro di cui eseguire il mapping alla proprietà
vtPropType
Valore che specifica il tipo della proprietà.
vtsParams
Stringa di spazio separata VTS_ per ogni parametro.
Osservazioni:
Queste macro consentono di specificare un DISPID anziché lasciare che MFC ne assegni automaticamente uno. Queste macro avanzate hanno gli stessi nomi, ad eccezione del fatto che l'ID viene aggiunto al nome della macro (ad esempio , DISP_PROPERTY_ID) e l'ID è determinato dal parametro specificato subito dopo il parametro pszName . Vedere AFXDISP. H per altre informazioni su queste macro. Le voci _ID devono essere posizionate alla fine della mappa di distribuzione. Influiranno sulla generazione automatica di DISPID nello stesso modo in cui una versione non _ID della macro (i DISPID sono determinati dalla posizione). Ad esempio:
BEGIN_DISPATCH_MAP(CDisp3DPoint, CCmdTarget)
DISP_PROPERTY(CDisp3DPoint, "y", m_y, VT_I2)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
DISP_PROPERTY_ID(CDisp3DPoint, "x", 0x00020003, m_x, VT_I2)
END_DISPATCH_MAP()
MFC genererà DISPID per la classe CDisp3DPoint come indicato di seguito:
property X (DISPID)0x00020003
property Y (DISPID)0x00000002
property Z (DISPID)0x00000001
Specificare un DISPID fisso è utile per mantenere la compatibilità con le versioni precedenti di un'interfaccia dispatch esistente o per implementare determinati metodi o proprietà definiti dal sistema (in genere indicati da un DISPID negativo, ad esempio la raccolta di DISPID_NEWENUM).
Recupero dell'interfaccia IDispatch per un oggetto COleClientItem
Molti server supporteranno l'automazione all'interno degli oggetti documento, insieme alla funzionalità del server OLE. Per ottenere l'accesso a questa interfaccia di automazione, è necessario accedere direttamente alla COleClientItem::m_lpObject
variabile membro. Il codice seguente recupererà l'interfaccia IDispatch
per un oggetto derivato da COleClientItem
. È possibile includere il codice seguente nell'applicazione se si trova questa funzionalità necessaria:
LPDISPATCH CMyClientItem::GetIDispatch()
{
ASSERT_VALID(this);
ASSERT(m_lpObject != NULL);
LPUNKNOWN lpUnk = m_lpObject;
Run(); // must be running
LPOLELINK lpOleLink = NULL;
if (m_lpObject->QueryInterface(IID_IOleLink,
(LPVOID FAR*)&lpOleLink) == NOERROR)
{
ASSERT(lpOleLink != NULL);
lpUnk = NULL;
if (lpOleLink->GetBoundSource(&lpUnk) != NOERROR)
{
TRACE0("Warning: Link is not connected!\n");
lpOleLink->Release();
return NULL;
}
ASSERT(lpUnk != NULL);
}
LPDISPATCH lpDispatch = NULL;
if (lpUnk->QueryInterface(IID_IDispatch, &lpDispatch) != NOERROR)
{
TRACE0("Warning: does not support IDispatch!\n");
return NULL;
}
ASSERT(lpDispatch != NULL);
return lpDispatch;
}
L'interfaccia dispatch restituita da questa funzione può quindi essere usata direttamente o collegata a un COleDispatchDriver
oggetto per l'accesso indipendente dai tipi. Se lo si usa direttamente, assicurarsi di chiamare il relativo Release
membro quando si passa con il puntatore (il COleDispatchDriver
distruttore esegue questa operazione per impostazione predefinita).