Sdílet prostřednictvím


TN065: Podpora duálního rozhraní u automatizačních serverů OLE

Poznámka

Následující technická poznámka se od prvního zahrnutí do online dokumentace neaktualizovala. V důsledku toho můžou být některé postupy a témata zastaralé nebo nesprávné. Nejnovější informace doporučujeme vyhledat v online indexu dokumentace, které vás zajímá.

Tato poznámka popisuje, jak přidat podporu duálního rozhraní do serverové aplikace OLE Automation založené na MFC. Ukázka ACDUAL znázorňuje podporu duálního rozhraní a ukázkový kód v této poznámce je převzat z ACDUAL. Makra popsaná v této poznámce, jako jsou například DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART a IMPLEMENT_DUAL_ERRORINFO, jsou součástí ukázky ACDUAL a najdete je v prostředí MFCDUAL.H.

Duální rozhraní

I když automatizace OLE umožňuje implementovat IDispatch rozhraní, rozhraní VTBL nebo duální rozhraní (které zahrnuje obojí), Microsoft důrazně doporučuje implementovat duální rozhraní pro všechny vystavené objekty AUTOMATIZACE OLE. Duální rozhraní mají významné výhody oproti IDispatchrozhraním jen pro VTBL:

  • Vazba může probíhat v době kompilace prostřednictvím rozhraní VTBL nebo v době běhu .IDispatch

  • Řadiče automatizace OLE, které můžou používat rozhraní VTBL, můžou těžit z vyššího výkonu.

  • Stávající řadiče automatizace OLE, které používají IDispatch rozhraní, budou nadále fungovat.

  • Rozhraní VTBL je jednodušší volat z jazyka C++.

  • Kvůli kompatibilitě s funkcemi podpory objektů jazyka Visual Basic se vyžadují duální rozhraní.

Přidání podpory duálního rozhraní do třídy založené na CCmdTarget

Duální rozhraní je opravdu jen vlastní rozhraní odvozené z IDispatch. Nejjednodušší způsob implementace podpory duálního rozhraní ve CCmdTargettřídě založené je nejprve implementovat normální dispečerské rozhraní třídy pomocí MFC a ClassWizard a pak přidat vlastní rozhraní později. Ve většině případů bude implementace vlastního rozhraní jednoduše delegovat zpět na implementaci MFC IDispatch .

Nejprve upravte soubor ODL pro váš server tak, aby definoval dvě rozhraní pro objekty. Chcete-li definovat duální rozhraní, je nutné použít příkaz rozhraní místo DISPINTERFACE příkazu, který průvodce Visual C++ generuje. Místo odebrání existujícího DISPINTERFACE příkazu přidejte nový příkaz rozhraní. Zachováním DISPINTERFACE formuláře můžete pokračovat v použití TřídyWizard přidat vlastnosti a metody do objektu, ale musíte přidat ekvivalentní vlastnosti a metody do příkazu rozhraní.

Příkaz rozhraní pro duální rozhraní musí mít OLEAUTOMATION a DUÁLNÍ atributy a rozhraní musí být odvozeno z IDispatch. Ukázku GUIDGEN můžete použít k vytvoření IID pro duální rozhraní:

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

Jakmile budete mít příkaz rozhraní na místě, začněte přidávat položky pro metody a vlastnosti. U duálních rozhraní musíte změnit uspořádání seznamů parametrů tak, aby metody a funkce přistupování vlastností v duálním rozhraní vrátily HODNOTU HRESULT a předaly jejich návratové hodnoty jako parametry s atributy [retval,out]. Nezapomeňte, že u vlastností budete muset přidat accessovou funkci pro čtení (propget) i zápis (propput) se stejným ID. Příklad:

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

Po definování metod a vlastností musíte do příkazu coclass přidat odkaz na příkaz rozhraní. Příklad:

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

Po aktualizaci souboru ODL použijte mechanismus mapování rozhraní MFC k definování třídy implementace pro duální rozhraní ve vaší třídě objektu a proveďte odpovídající položky v mechanismu MFC QueryInterface . Potřebujete jednu položku v INTERFACE_PART bloku pro každou položku v rozhraní příkazu ODL a položky pro rozhraní dispečera. Každá položka ODL s atributem propput potřebuje funkci s názvem put_propertyname. Každá položka s atributem propget potřebuje funkci s názvem get_propertyname.

Pokud chcete definovat implementační třídu pro duální rozhraní, přidejte do definice třídy objektu DUAL_INTERFACE_PART blok. Příklad:

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)

Pokud chcete připojit duální rozhraní k mechanismu QueryInterface knihovny MFC, přidejte INTERFACE_PART do mapy rozhraní položku:

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

Dále je potřeba vyplnit implementaci rozhraní. Ve většině případů budete moct delegovat na existující implementaci MFC IDispatch . Příklad:

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

Pro metody objektu a funkce přistupování vlastností je nutné vyplnit implementaci. Funkce vaší metody a vlastností mohou obecně delegovat zpět na metody generované pomocí TřídyWizard. Pokud ale nastavíte vlastnosti pro přímý přístup k proměnným, musíte napsat kód, který získá nebo vloží hodnotu do proměnné. Příklad:

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

Předávání ukazatelů duálního rozhraní

Předání ukazatele duálního rozhraní není jednoduché, zejména pokud potřebujete volat CCmdTarget::FromIDispatch. FromIDispatch Funguje pouze u ukazatelů MFC IDispatch . Jedním ze způsobů, jak to obejít, je dotazování na původní IDispatch ukazatel nastavený v prostředí MFC a předání tohoto ukazatele funkcím, které ho potřebují. Příklad:

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

Před předáním ukazatele zpět metodou duálního rozhraní může být nutné jej převést z ukazatele MFC IDispatch na ukazatel s duálním rozhraním. Příklad:

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

Registrace knihovny typů aplikace

AppWizard negeneruje kód pro registraci knihovny typů serverové aplikace OLE Automation v systému. I když existují další způsoby registrace knihovny typů, je vhodné, aby aplikace registrovala knihovnu typů při aktualizaci informací o typu OLE, to znamená, kdykoli je aplikace spuštěna samostatně.

Pokud chcete zaregistrovat knihovnu typů aplikace vždy, když je aplikace spuštěná samostatně:

  • Zahrnout AFXCTL. H ve vašem standardu obsahuje hlavičkový soubor STDAFX. H, pro přístup k definici AfxOleRegisterTypeLib funkce.

  • Ve funkci aplikace InitInstance vyhledejte volání COleObjectFactory::UpdateRegistryAll. Za tímto voláním přidejte volání AfxOleRegisterTypeLib, zadejte LIBID odpovídající knihovně typů spolu s názvem knihovny typů:

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

Úprava Nastavení sestavení projektu tak, aby vyhovovala změnám knihovny typů

Chcete-li upravit nastavení sestavení projektu tak, aby soubor záhlaví obsahující definice UUID byl generován mkTypLib při každém opětovném vytvoření knihovny typů:

  1. V nabídce Sestavení klepněte na Nastavení a pak vyberte soubor ODL ze seznamu souborů pro každou konfiguraci.

  2. Klikněte na kartu Typy OLE a zadejte název souboru v poli Název souboru záhlaví výstupu. Použijte název souboru, který váš projekt ještě nepoužívá, protože MkTypLib přepíše všechny existující soubory. Kliknutím na tlačítko OK zavřete dialogové okno Sestavení Nastavení.

Přidání definic UUID ze souboru hlaviček vygenerovaného v MkTypLib do projektu:

  1. Do standardního standardu zahrňte soubor hlaviček vygenerovaný mkTypLib, včetně souboru hlavičky stdafx.h.

  2. Vytvořte nový soubor INITIIDS. CPP a přidejte ho do projektu. Do tohoto souboru zahrňte soubor hlaviček vygenerovaný v MkTypLib po zahrnutí OLE2. H a 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. V nabídce Sestavení klepněte na Nastavení a pak vyberte INITIIDS. CPP ze seznamu souborů pro každou konfiguraci.

  4. Klikněte na kartu C++ , klikněte na kategorii Předkompilované hlavičky a vyberte přepínač Nepou ítát předkompilované záhlaví . Kliknutím na tlačítko OK zavřete dialogové okno Nastavení sestavení.

Zadání správného názvu třídy objektu v knihovně typů

Průvodci dodávané s jazykem Visual C++ nesprávně používají název třídy implementace k určení třídy coclass v souboru ODL serveru pro třídy OLE-creatable. I když to bude fungovat, název třídy implementace pravděpodobně není název třídy, který chcete, aby uživatelé objektu používali. Chcete-li zadat správný název, otevřete soubor ODL, vyhledejte každý příkaz coclass a nahraďte název třídy implementace správným externím názvem.

Všimněte si, že při změně příkazu coclass se názvy proměnných CLSIDs v souboru hlaviček generovaném mkTypLib odpovídajícím způsobem změní. Budete muset aktualizovat kód tak, aby používal nové názvy proměnných.

Zpracovánívýjimekch

Metody objektu automatizace a funkce přístupových objektů vlastností mohou vyvolat výjimky. Pokud ano, měli byste je zpracovat ve své implementaci duálního rozhraní a předat informace o výjimce zpět kontroleru prostřednictvím rozhraní zpracování chyb OLE Automation, IErrorInfo. Toto rozhraní poskytuje podrobné kontextové informace o chybách prostřednictvím IDispatch rozhraní VTBL i rozhraní VTBL. Chcete-li označit, že je k dispozici obslužná rutina chyby, měli byste implementovat ISupportErrorInfo rozhraní.

Pokud chcete ilustrovat mechanismus zpracování chyb, předpokládejme, že funkce generované classWizard, které se používají k implementaci standardní podpory odesílání vyvolat výjimky. Implementace IDispatch::Invoke mfc obvykle tyto výjimky zachytí a převede je na strukturu EXCEPTINFO, která se vrátí prostřednictvím Invoke volání. Při použití rozhraní VTBL však zodpovídáte za zachycení výjimek sami. Příkladem ochrany metod s duálním rozhraním:

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 při výskytu výjimky se postará o vrácení správného kódu chyby. CATCH_ALL_DUAL převede výjimku MFC na informace o zpracování chyb při automatizaci OLE pomocí ICreateErrorInfo rozhraní. (Ukázkové CATCH_ALL_DUAL makro je v souboru MFCDUAL. H v ukázce ACDUAL . Funkce, která volá zpracování výjimek, DualHandleExceptionje v souboru MFCDUAL. CPP.) CATCH_ALL_DUAL určuje kód chyby, který se má vrátit na základě typu výjimky, ke které došlo:

  • COleDispatchException – v tomto případě HRESULT se konstruuje pomocí následujícího kódu:

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

    Tím se vytvoří HRESULT specifické pro rozhraní, které způsobilo výjimku. Kód chyby je posunut 0x200, aby nedocházelo ke konfliktům se systémem definovanými HRESULTsystémy pro standardní rozhraní OLE.

  • CMemoryException – v tomto případě E_OUTOFMEMORY se vrátí.

  • Jakákoli jiná výjimka – v tomto případě E_UNEXPECTED se vrátí.

Chcete-li označit, že se používá obslužná rutina chyby OLE Automation, měli byste také implementovat ISupportErrorInfo rozhraní.

Nejprve přidejte do definice třídy automatizace kód, který ukazuje, že podporuje ISupportErrorInfo.

Za druhé přidejte do mapy rozhraní třídy automatizace kód, který přidruží ISupportErrorInfo třídu implementace k mechanismu MFC QueryInterface . Příkaz INTERFACE_PART odpovídá třídě definované pro ISupportErrorInfo.

Nakonec implementujte třídu definovanou pro podporu ISupportErrorInfo.

(Ukázka ACDUAL obsahuje tři makra, která vám pomohou provést tyto tři kroky, DUAL_ERRORINFO_PARTDECLARE_DUAL_ERRORINFOa IMPLEMENT_DUAL_ERRORINFOvšechny obsažené v MFCDUAL.H.)

Následující příklad implementuje třídu definovanou pro podporu ISupportErrorInfo. CAutoClickDoc je název vaší třídy automatizace a IID_IDualAClick je IID pro rozhraní, které je zdrojem chyb hlášených prostřednictvím objektu chyby AUTOMATIZACE 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;
}

Viz také

Technické poznámky podle čísel
Technické poznámky podle kategorií