TN065: supporto di interfaccia duale per i server di automazione 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.

Questa nota illustra come aggiungere il supporto a doppia interfaccia a un'applicazione server di automazione OLE basata su MFC. L'esempio ACDUAL illustra il supporto a doppia interfaccia e il codice di esempio in questa nota è tratto da ACDUAL. Le macro descritte in questa nota, ad esempio DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART e IMPLEMENT_DUAL_ERRORINFO, fanno parte dell'esempio ACDUAL e sono disponibili in MFCDUAL.H.

Interfacce doppie

Anche se l'automazione OLE consente di implementare un'interfaccia IDispatch , un'interfaccia VTBL o una doppia interfaccia (che include entrambi), Microsoft consiglia vivamente di implementare interfacce doppie per tutti gli oggetti di automazione OLE esposti. Le interfacce duali presentano vantaggi significativi rispetto IDispatchalle interfacce solo -only o VTBL-only:

  • L'associazione può essere eseguita in fase di compilazione tramite l'interfaccia VTBL o in fase di esecuzione tramite IDispatch.

  • I controller di automazione OLE che possono usare l'interfaccia VTBL possono trarre vantaggio da prestazioni migliorate.

  • I controller di automazione OLE esistenti che usano l'interfaccia IDispatch continueranno a funzionare.

  • L'interfaccia VTBL è più semplice da chiamare da C++.

  • Le interfacce doppie sono necessarie per la compatibilità con le funzionalità di supporto degli oggetti visual Basic.

Aggiunta del supporto a doppia interfaccia a una classe basata su CCmdTarget

Un'interfaccia duale è in realtà solo un'interfaccia personalizzata derivata da IDispatch. Il modo più semplice per implementare il supporto a doppia interfaccia in una CCmdTargetclasse basata su consiste innanzitutto nell'implementare la normale interfaccia dispatch nella classe usando MFC e ClassWizard, quindi aggiungere l'interfaccia personalizzata in un secondo momento. Per la maggior parte, l'implementazione dell'interfaccia personalizzata delega semplicemente all'implementazione MFC IDispatch .

Prima di tutto, modificare il file ODL per il server per definire interfacce doppie per gli oggetti. Per definire un'interfaccia doppia, è necessario usare un'istruzione di interfaccia anziché l'istruzione DISPINTERFACE generata dalle procedure guidate di Visual C++. Anziché rimuovere l'istruzione esistente DISPINTERFACE , aggiungere una nuova istruzione di interfaccia. Mantenendo il DISPINTERFACE modulo, è possibile continuare a usare ClassWizard per aggiungere proprietà e metodi all'oggetto, ma è necessario aggiungere le proprietà e i metodi equivalenti all'istruzione dell'interfaccia.

Un'istruzione di interfaccia per un'interfaccia duale deve avere gli attributi OLEAUTOMATION e DUAL e l'interfaccia deve essere derivata da IDispatch. È possibile usare l'esempio GUIDGEN per creare un IID per l'interfaccia duale:

[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
    oleautomation,
    dual
]
interface IDualAClick : IDispatch
    {
    };

Dopo aver creato l'istruzione di interfaccia, iniziare ad aggiungere voci per i metodi e le proprietà. Per le interfacce duali, è necessario ridisporre gli elenchi di parametri in modo che i metodi e le funzioni di accesso alle proprietà nell'interfaccia doppia restituiscano un HRESULT e passino i valori restituiti come parametri con gli attributi [retval,out]. Tenere presente che per le proprietà sarà necessario aggiungere sia una funzione di accesso di lettura (propget) che di scrittura (propput) con lo stesso ID. Per esempio:

[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);

Dopo aver definito i metodi e le proprietà, è necessario aggiungere un riferimento all'istruzione interface nell'istruzione coclasse. Ad esempio:

[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
    dispinterface IAClick;
    [default] interface IDualAClick;
};

Dopo aver aggiornato il file ODL, usare il meccanismo di mapping dell'interfaccia MFC per definire una classe di implementazione per l'interfaccia duale nella classe oggetto e impostare le voci corrispondenti nel meccanismo MFC QueryInterface . È necessaria una voce nel INTERFACE_PART blocco per ogni voce nell'istruzione di interfaccia dell'ODL, più le voci per un'interfaccia dispatch. Ogni voce ODL con l'attributo propput richiede una funzione denominata put_propertyname. Ogni voce con l'attributo propget richiede una funzione denominata get_propertyname.

Per definire una classe di implementazione per l'interfaccia duale, aggiungere un DUAL_INTERFACE_PART blocco alla definizione della classe oggetto. Ad esempio:

BEGIN_DUAL_INTERFACE_PART(DualAClick, IDualAClick)
    STDMETHOD(put_text)(THIS_ BSTR newText);
    STDMETHOD(get_text)(THIS_ BSTR FAR* retval);
    STDMETHOD(put_x)(THIS_ short newX);
    STDMETHOD(get_x)(THIS_ short FAR* retval);
    STDMETHOD(put_y)(THIS_ short newY);
    STDMETHOD(get_y)(THIS_ short FAR* retval);
    STDMETHOD(put_Position)(THIS_ IDualAutoClickPoint FAR* newPosition);
    STDMETHOD(get_Position)(THIS_ IDualAutoClickPoint FAR* FAR* retval);
    STDMETHOD(RefreshWindow)(THIS);
    STDMETHOD(SetAllProps)(THIS_ short x, short y, BSTR text);
    STDMETHOD(ShowWindow)(THIS);
END_DUAL_INTERFACE_PART(DualAClick)

Per connettere l'interfaccia doppia al meccanismo QueryInterface di MFC, aggiungere una INTERFACE_PART voce alla mappa dell'interfaccia:

BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
    INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
    INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()

Successivamente, è necessario compilare l'implementazione dell'interfaccia. Nella maggior parte dei casi, sarà possibile delegare all'implementazione MFC IDispatch esistente. Ad esempio:

STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::AddRef()
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::Release()
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalRelease();
}

STDMETHODIMP CAutoClickDoc::XDualAClick::QueryInterface(
    REFIID iid,
    LPVOID* ppvObj)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfoCount(
    UINT FAR* pctinfo)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);
    return lpDispatch->GetTypeInfoCount(pctinfo);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfo(
    UINT itinfo,
    LCID lcid,
    ITypeInfo FAR* FAR* pptinfo)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    UINT cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    WORD wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    UINT FAR* puArgErr)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->Invoke(dispidMember, riid, lcid,
        wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}

Per i metodi e le funzioni di accesso alle proprietà dell'oggetto, è necessario compilare l'implementazione. Le funzioni di metodo e proprietà possono in genere delegare i metodi generati tramite ClassWizard. Tuttavia, se si configurano le proprietà per accedere direttamente alle variabili, è necessario scrivere il codice per ottenere/inserire il valore nella variabile. Ad esempio:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    // MFC automatically converts from Unicode BSTR to
    // Ansi CString, if necessary...
    pThis->m_str = newText;
    return NOERROR;
}

STDMETHODIMP CAutoClickDoc::XDualAClick::get_text(BSTR* retval)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    // MFC automatically converts from Ansi CString to
    // Unicode BSTR, if necessary...
    pThis->m_str.SetSysString(retval);
    return NOERROR;
}

Passaggio di puntatori a doppia interfaccia

Il passaggio del puntatore a doppia interfaccia non è semplice, soprattutto se è necessario chiamare CCmdTarget::FromIDispatch. FromIDispatch funziona solo sui puntatori MFC IDispatch . Un modo per risolvere questo problema consiste nell'eseguire una query per il puntatore originale IDispatch configurato da MFC e passare tale puntatore alle funzioni necessarie. Ad esempio:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_Position(
    IDualAutoClickPoint FAR* newPosition)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDisp = NULL;
    newPosition->QueryInterface(IID_IDispatch, (LPVOID*)&lpDisp);
    pThis->SetPosition(lpDisp);
    lpDisp->Release();
    return NOERROR;
}

Prima di passare un puntatore al metodo a doppia interfaccia, potrebbe essere necessario convertirlo dal puntatore MFC IDispatch al puntatore a doppia interfaccia. Ad esempio:

STDMETHODIMP CAutoClickDoc::XDualAClick::get_Position(
    IDualAutoClickPoint FAR* FAR* retval)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDisp;
    lpDisp = pThis->GetPosition();
    lpDisp->QueryInterface(IID_IDualAutoClickPoint, (LPVOID*)retval);
    return NOERROR;
}

Registrazione della libreria dei tipi dell'applicazione

AppWizard non genera codice per registrare la libreria dei tipi di un'applicazione server di automazione OLE con il sistema. Anche se esistono altri modi per registrare la libreria dei tipi, è utile che l'applicazione registri la libreria dei tipi quando aggiorna le informazioni sul tipo OLE, ovvero ogni volta che l'applicazione viene eseguita autonoma.

Per registrare la libreria dei tipi dell'applicazione ogni volta che l'applicazione viene eseguita in modalità autonoma:

  • Includere AFXCTL. H nello standard include il file di intestazione STDAFX. H, per accedere alla definizione della AfxOleRegisterTypeLib funzione.

  • Nella funzione dell'applicazione InitInstance individuare la chiamata a COleObjectFactory::UpdateRegistryAll. Dopo questa chiamata, aggiungere una chiamata a AfxOleRegisterTypeLib, specificando il LIBID corrispondente alla libreria dei tipi, insieme al nome della libreria dei tipi:

    // When a server application is launched stand-alone, it is a good idea
    // to update the system registry in case it has been damaged.
    m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
    
    COleObjectFactory::UpdateRegistryAll();
    
    // DUAL_SUPPORT_START
        // Make sure the type library is registered or dual interface won't work.
        AfxOleRegisterTypeLib(AfxGetInstanceHandle(),
            LIBID_ACDual,
            _T("AutoClik.TLB"));
    // DUAL_SUPPORT_END
    

Modifica del Impostazioni di compilazione del progetto in base alle modifiche della libreria dei tipi

Per modificare le impostazioni di compilazione di un progetto in modo che un file di intestazione contenente definizioni UUID venga generato da MkTypLib ogni volta che viene ricompilata la libreria dei tipi:

  1. Scegliere Impostazioni dal menu Compila e quindi selezionare il file ODL dall'elenco di file per ogni configurazione.

  2. Fare clic sulla scheda Tipi OLE e specificare un nome file nel campo Nome file intestazione output. Usare un nome file non già usato dal progetto, perché MkTypLib sovrascriverà qualsiasi file esistente. Fare clic su OK per chiudere la finestra di dialogo Compila Impostazioni.

Per aggiungere le definizioni UUID dal file di intestazione generato da MkTypLib al progetto:

  1. Includere il file di intestazione MkTypLib nel file di intestazione standard include il file di intestazione stdafx.h.

  2. Creare un nuovo file, INITIIDS. CPP e aggiungerlo al progetto. In questo file includere il file di intestazione generato da MkTypLib dopo aver incluso OLE2. H e INITGUID. H:

    // initIIDs.c: defines IIDs for dual interfaces
    // This must not be built with precompiled header.
    #include <ole2.h>
    #include <initguid.h>
    #include "acdual.h"
    
  3. Scegliere Impostazioni dal menu Compila e quindi selezionare INITIIDS. CPP dall'elenco di file per ogni configurazione.

  4. Fare clic sulla scheda C++ , fare clic sulla categoria Intestazioni precompilate e selezionare il pulsante di opzione Non usando intestazioni precompilate . Fare clic su OK per chiudere la finestra di dialogo Compila Impostazioni.

Specifica del nome corretto della classe oggetto nella libreria dei tipi

Le procedure guidate fornite con Visual C++ usano erroneamente il nome della classe di implementazione per specificare la coclasse nel file ODL del server per le classi creabili OLE. Anche se funzionerà, il nome della classe di implementazione probabilmente non è il nome della classe che si vuole che gli utenti dell'oggetto usino. Per specificare il nome corretto, aprire il file ODL, individuare ogni istruzione coclasse e sostituire il nome della classe di implementazione con il nome esterno corretto.

Si noti che quando viene modificata l'istruzione coclasse, i nomi delle variabili di CLSIDnel file di intestazione generato da MkTypLib cambieranno di conseguenza. Sarà necessario aggiornare il codice per usare i nuovi nomi di variabile.

Gestione delle eccezioni e delle interfacce degli errori di automazione

I metodi e le funzioni di accesso alle proprietà dell'oggetto di automazione possono generare eccezioni. In questo caso, è necessario gestirli nell'implementazione a doppia interfaccia e passare le informazioni sull'eccezione al controller tramite l'interfaccia di gestione degli errori di automazione OLE, IErrorInfo. Questa interfaccia fornisce informazioni dettagliate sull'errore contestuale tramite entrambe IDispatch le interfacce VTBL. Per indicare che è disponibile un gestore errori, è necessario implementare l'interfaccia ISupportErrorInfo .

Per illustrare il meccanismo di gestione degli errori, si supponga che le funzioni generate da ClassWizard usate per implementare il supporto dispatch standard generino eccezioni. L'implementazione di MFC di IDispatch::Invoke rileva in genere queste eccezioni e le converte in una struttura EXCEPTINFO restituita tramite la Invoke chiamata. Tuttavia, quando viene usata l'interfaccia VTBL, l'utente è responsabile dell'intercettazione delle eccezioni. Come esempio di protezione dei metodi a doppia interfaccia:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    TRY_DUAL(IID_IDualAClick)
    {
        // MFC automatically converts from Unicode BSTR to
        // Ansi CString, if necessary...
        pThis->m_str = newText;
        return NOERROR;
    }
    CATCH_ALL_DUAL
}

CATCH_ALL_DUAL si occupa della restituzione del codice di errore corretto quando si verifica un'eccezione. CATCH_ALL_DUAL converte un'eccezione MFC in informazioni sulla gestione degli errori di automazione OLE usando l'interfaccia ICreateErrorInfo . Una macro di esempio CATCH_ALL_DUAL si trova nel file MFCDUAL. H nell'esempio ACDUAL . La funzione chiama per gestire le eccezioni, DualHandleException, si trova nel file MFCDUAL. CPP. CATCH_ALL_DUAL determina il codice di errore da restituire in base al tipo di eccezione che si è verificato:

  • COleDispatchException : in questo caso, HRESULT viene costruito usando il codice seguente:

    hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
    

    Viene creato un oggetto HRESULT specifico dell'interfaccia che ha causato l'eccezione. Il codice di errore viene scostato da 0x200 per evitare conflitti con s definiti dal HRESULTsistema per le interfacce OLE standard.

  • CMemoryException : in questo caso, E_OUTOFMEMORY viene restituito .

  • Qualsiasi altra eccezione: in questo caso, E_UNEXPECTED viene restituita.

Per indicare che viene usato il gestore degli errori di automazione OLE, è necessario implementare anche l'interfaccia ISupportErrorInfo .

Aggiungere prima di tutto il codice alla definizione della classe di automazione per mostrare che supporta ISupportErrorInfo.

In secondo luogo, aggiungere codice alla mappa dell'interfaccia della classe di automazione per associare la ISupportErrorInfo classe di implementazione al meccanismo MFC QueryInterface . L'istruzione INTERFACE_PART corrisponde alla classe definita per ISupportErrorInfo.

Implementare infine la classe definita per supportare ISupportErrorInfo.

(L'oggetto L'esempio ACDUAL contiene tre macro che consentono di eseguire questi tre passaggi, DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PARTe IMPLEMENT_DUAL_ERRORINFO, tutti contenuti in MFCDUAL.H.

Nell'esempio seguente viene implementata una classe definita per supportare ISupportErrorInfo. CAutoClickDocè il nome della classe di automazione ed IID_IDualAClick è l'IID per l'interfaccia che rappresenta l'origine degli errori segnalati tramite l'oggetto errore di automazione OLE:

STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::AddRef()
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::Release()
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalRelease();
}

STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::QueryInterface(
    REFIID iid,
    LPVOID* ppvObj)
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::InterfaceSupportsErrorInfo(
    REFIID iid)
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return (iid == IID_IDualAClick) S_OK : S_FALSE;
}

Vedi anche

Note tecniche per numero
Note tecniche per categoria