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 IDispatch
rozhraní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 CCmdTarget
tří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ů:
V nabídce Sestavení klepněte na Nastavení a pak vyberte soubor ODL ze seznamu souborů pro každou konfiguraci.
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:
Do standardního standardu zahrňte soubor hlaviček vygenerovaný mkTypLib, včetně souboru hlavičky stdafx.h.
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"
V nabídce Sestavení klepněte na Nastavení a pak vyberte INITIIDS. CPP ze seznamu souborů pro každou konfiguraci.
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, DualHandleException
je 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ýmiHRESULT
systé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_PART
DECLARE_DUAL_ERRORINFO
a IMPLEMENT_DUAL_ERRORINFO
vš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í