TN065: obsługa podwójnego interfejsu w przypadku serwerów automatyzacji OLE
Uwaga
Następująca uwaga techniczna nie została zaktualizowana, ponieważ została po raz pierwszy uwzględniona w dokumentacji online. W związku z tym niektóre procedury i tematy mogą być nieaktualne lub nieprawidłowe. Aby uzyskać najnowsze informacje, zaleca się wyszukanie interesującego tematu w indeksie dokumentacji online.
W tej notatce omówiono sposób dodawania obsługi podwójnego interfejsu do aplikacji serwera OLE Automation opartej na MFC. Przykład ACDUAL ilustruje obsługę dwóch interfejsów, a przykładowy kod w tej notatce jest pobierany z usługi ACDUAL. Makra opisane w tej notatce, takie jak DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART i IMPLEMENT_DUAL_ERRORINFO, są częścią przykładu ACDUAL i można je znaleźć w MFCDUAL.H.
Podwójne interfejsy
Mimo że automatyzacja OLE umożliwia zaimplementowanie interfejsu, interfejsu IDispatch
VTBL lub podwójnego interfejsu (który obejmuje oba), firma Microsoft zdecydowanie zaleca zaimplementowanie podwójnych interfejsów dla wszystkich uwidocznionych obiektów automatyzacji OLE. Interfejsy podwójne mają znaczne zalety IDispatch
w porównaniu tylko z interfejsami tylko i VTBL:
Powiązanie może odbywać się w czasie kompilacji za pośrednictwem interfejsu VTBL lub w czasie wykonywania za pośrednictwem
IDispatch
.Kontrolery automatyzacji OLE, które mogą korzystać z interfejsu VTBL, mogą korzystać z lepszej wydajności.
Istniejące kontrolery automatyzacji OLE korzystające z interfejsu
IDispatch
będą nadal działać.Interfejs VTBL jest łatwiejszy do wywołania z języka C++.
Do zapewnienia zgodności z funkcjami obsługi obiektów języka Visual Basic wymagane są dwa interfejsy.
Dodawanie obsługi dwóch interfejsów do klasy opartej na CCmdTarget
Podwójny interfejs jest naprawdę tylko interfejsem niestandardowym pochodzącym z IDispatch
klasy . Najprostszym sposobem zaimplementowania obsługi dwóch interfejsów w klasie opartej CCmdTarget
na interfejsie jest najpierw zaimplementowanie normalnego interfejsu wysyłania w klasie przy użyciu MFC i ClassWizard, a następnie dodanie interfejsu niestandardowego później. W większości przypadków implementacja interfejsu niestandardowego będzie po prostu delegować z powrotem do implementacji MFC IDispatch
.
Najpierw zmodyfikuj plik ODL serwera, aby zdefiniować podwójne interfejsy dla obiektów. Aby zdefiniować podwójny interfejs, należy użyć instrukcji interfejsu, zamiast DISPINTERFACE
instrukcji generowanej przez kreatorów języka Visual C++. Zamiast usuwać istniejącą DISPINTERFACE
instrukcję, dodaj nową instrukcję interfejsu. DISPINTERFACE
Zachowując formularz, możesz nadal używać klasy ClassWizard do dodawania właściwości i metod do obiektu, ale musisz dodać równoważne właściwości i metody do instrukcji interfejsu.
Instrukcja interfejsu dla podwójnego interfejsu musi mieć atrybuty OLEAUTOMATION i DUAL , a interfejs musi pochodzić z IDispatch
klasy . Możesz użyć przykładu GUIDGEN , aby utworzyć identyfikator IID dla podwójnego interfejsu:
[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
oleautomation,
dual
]
interface IDualAClick : IDispatch
{
};
Po utworzeniu instrukcji interfejsu zacznij dodawać wpisy dla metod i właściwości. W przypadku podwójnych interfejsów należy zmienić kolejność list parametrów, aby metody i funkcje dostępu właściwości w interfejsie podwójnym zwracały wartość HRESULT i przekazywać ich wartości zwracane jako parametry z atrybutami [retval,out]
. Należy pamiętać, że w przypadku właściwości należy dodać zarówno funkcję dostępu do odczytu (propget
), jak i zapisu (propput
) o tym samym identyfikatorze. Na przykład:
[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);
Po zdefiniowaniu metod i właściwości należy dodać odwołanie do instrukcji interfejsu w instrukcji coclass. Przykład:
[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
dispinterface IAClick;
[default] interface IDualAClick;
};
Po zaktualizowaniu pliku ODL użyj mechanizmu mapy interfejsu MFC, aby zdefiniować klasę implementacji dla podwójnego interfejsu w klasie obiektów i wprowadzić odpowiednie wpisy w mechanizmie QueryInterface
MFC. Potrzebny jest jeden wpis w INTERFACE_PART
bloku dla każdego wpisu w instrukcji interfejsu ODL oraz wpisy dla interfejsu wysyłania. Każdy wpis ODL z atrybutem propput wymaga funkcji o nazwie put_propertyname
. Każdy wpis z atrybutem propget wymaga funkcji o nazwie get_propertyname
.
Aby zdefiniować klasę implementacji dla podwójnego interfejsu, dodaj DUAL_INTERFACE_PART
blok do definicji klasy obiektów. Przykład:
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)
Aby połączyć podwójny interfejs z mechanizmem QueryInterface MFC, dodaj INTERFACE_PART
wpis do mapy interfejsu:
BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()
Następnie należy wypełnić implementację interfejsu. W większości przypadków będzie można delegować do istniejącej implementacji MFC IDispatch
. Przykład:
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);
}
W przypadku metod i funkcji dostępu właściwości obiektu należy wypełnić implementację. Funkcje metody i właściwości mogą zwykle delegować z powrotem do metod wygenerowanych przy użyciu klasy ClassWizard. Jeśli jednak skonfigurujesz właściwości, aby uzyskać bezpośredni dostęp do zmiennych, musisz napisać kod, aby pobrać/umieścić wartość w zmiennej. Przykład:
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;
}
Przekazywanie podwójnych wskaźników interfejsu
Przekazywanie wskaźnika podwójnego interfejsu nie jest proste, zwłaszcza jeśli trzeba wywołać metodę CCmdTarget::FromIDispatch
. FromIDispatch
działa tylko na wskaźnikach MFC IDispatch
. Jednym ze sposobów obejścia tego problemu jest wykonanie zapytania o oryginalny IDispatch
wskaźnik skonfigurowany przez MFC i przekazanie tego wskaźnika do funkcji, które ich potrzebują. Przykład:
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;
}
Przed przekazaniem wskaźnika z powrotem przez metodę podwójnego interfejsu może być konieczne przekonwertowanie go ze wskaźnika MFC IDispatch
na wskaźnik dwu interfejsu. Przykład:
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;
}
Rejestrowanie biblioteki typów aplikacji
Aplikacja AppWizard nie generuje kodu w celu zarejestrowania biblioteki typów aplikacji serwera OLE Automation w systemie. Chociaż istnieją inne sposoby rejestrowania biblioteki typów, wygodne jest zarejestrowanie przez aplikację biblioteki typów podczas aktualizowania informacji o typie OLE, czyli za każdym razem, gdy aplikacja jest uruchamiana autonomicznie.
Aby zarejestrować bibliotekę typów aplikacji za każdym razem, gdy aplikacja jest uruchamiana autonomicznie:
Dołącz bibliotekę AFXCTL. H w standardzie zawiera plik nagłówka STDAFX. H, aby uzyskać dostęp do definicji
AfxOleRegisterTypeLib
funkcji.W funkcji aplikacji
InitInstance
znajdź wywołanie metodyCOleObjectFactory::UpdateRegistryAll
. Zgodnie z tym wywołaniem dodaj wywołanie metody ,AfxOleRegisterTypeLib
określając identyfikator LIBID odpowiadający bibliotece typów wraz z nazwą biblioteki typów:// 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
Modyfikowanie Ustawienia kompilacji projektu w celu uwzględnienia zmian w bibliotece typów
Aby zmodyfikować ustawienia kompilacji projektu w taki sposób, aby plik nagłówkowy zawierający definicje UUID był generowany przez bibliotekę MkTypLib za każdym razem, gdy biblioteka typów zostanie ponownie skompilowana:
W menu Kompilacja kliknij Ustawienia, a następnie wybierz plik ODL z listy plików dla każdej konfiguracji.
Kliknij kartę Typy OLE i określ nazwę pliku w polu Nazwa pliku nagłówka danych wyjściowych. Użyj nazwy pliku, która nie jest jeszcze używana przez projekt, ponieważ mkTypLib zastąpi istniejący plik. Kliknij przycisk OK, aby zamknąć okno dialogowe Kompilacja Ustawienia.
Aby dodać definicje UUID z pliku nagłówka wygenerowanego przez bibliotekę MkTypLib do projektu:
Uwzględnij plik nagłówka wygenerowany przez bibliotekę MkTypLib w standardzie zawierający plik nagłówka stdafx.h.
Utwórz nowy plik INITIIDS. CPP i dodaj go do projektu. W tym pliku dołącz plik nagłówka wygenerowany przez bibliotekę MkTypLib po dołączeniu do pliku OLE2. H i 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"
W menu Kompilacja kliknij pozycję Ustawienia, a następnie wybierz pozycję INITIIDS. Program CPP z listy plików dla każdej konfiguracji.
Kliknij kartę C++, kliknij pozycję Prekompilowane nagłówki kategorii i wybierz przycisk radiowy Nieużywane prekompilowane nagłówki. Kliknij przycisk OK, aby zamknąć okno dialogowe Kompilacja Ustawienia.
Określanie poprawnej nazwy klasy obiektu w bibliotece typów
Kreatory dostarczane z programem Visual C++ niepoprawnie używają nazwy klasy implementacji, aby określić klasę współklasy w pliku ODL serwera dla klas OLE-creatable. Chociaż będzie to działać, nazwa klasy implementacji prawdopodobnie nie jest nazwą klasy, której użytkownicy obiektu mają używać. Aby określić poprawną nazwę, otwórz plik ODL, znajdź każdą instrukcję coclass i zastąp nazwę klasy implementacji poprawną nazwą zewnętrzną.
Należy pamiętać, że po zmianie instrukcji coclass nazwy zmiennych CLSIDs w pliku nagłówka wygenerowanego przez bibliotekę MkTypLib zostaną odpowiednio zmienione. Należy zaktualizować kod, aby używał nowych nazw zmiennych.
Obsługa wyjątków i interfejsów błędów automatyzacji
Metody i funkcje dostępu właściwości obiektu automatyzacji mogą zgłaszać wyjątki. Jeśli tak, należy je obsłużyć w implementacji podwójnego interfejsu i przekazać informacje o wyjątku z powrotem do kontrolera za pośrednictwem interfejsu obsługi błędów automatyzacji OLE, IErrorInfo
. Ten interfejs zapewnia szczegółowe informacje o błędach kontekstowych za pośrednictwem interfejsów I IDispatch
VTBL. Aby wskazać, że program obsługi błędów jest dostępny, należy zaimplementować ISupportErrorInfo
interfejs.
Aby zilustrować mechanizm obsługi błędów, załóżmy, że funkcje wygenerowane przez klasę ClassWizard używane do implementowania standardowej obsługi wysyłania zgłaszają wyjątki. Implementacja MFC zwykle IDispatch::Invoke
przechwytuje te wyjątki i konwertuje je na strukturę EXCEPTINFO zwracaną przez Invoke
wywołanie. Jeśli jednak używany jest interfejs VTBL, odpowiadasz za przechwycenie wyjątków samodzielnie. Przykładem ochrony metod podwójnego interfejsu:
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
zwraca prawidłowy kod błędu w przypadku wystąpienia wyjątku. CATCH_ALL_DUAL
Konwertuje wyjątek MFC na informacje o obsłudze błędów automatyzacji OLE przy użyciu interfejsu ICreateErrorInfo
. (Przykładowe CATCH_ALL_DUAL
makro znajduje się w pliku MFCDUAL. H w przykładzie ACDUAL . Funkcja, która wywołuje obsługę wyjątków, DualHandleException
znajduje się w pliku MFCDUAL. CPP).) CATCH_ALL_DUAL
określa kod błędu, który ma być zwracany na podstawie typu wyjątku, który wystąpił:
COleDispatchException — w tym przypadku
HRESULT
jest tworzony przy użyciu następującego kodu:hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
Spowoduje to utworzenie określonego
HRESULT
interfejsu, który spowodował wyjątek. Kod błędu jest przesunięty przez 0x200, aby uniknąć konfliktów ze standardowymi interfejsami OLE zdefiniowanymi przezHRESULT
system.CMemoryException — w tym przypadku
E_OUTOFMEMORY
jest zwracany.Każdy inny wyjątek — w tym przypadku
E_UNEXPECTED
jest zwracany.
Aby wskazać, że jest używana procedura obsługi błędów automatyzacji OLE, należy również zaimplementować ISupportErrorInfo
interfejs.
Najpierw dodaj kod do definicji klasy automatyzacji, aby pokazać, że obsługuje .ISupportErrorInfo
Po drugie dodaj kod do mapy interfejsu klasy automatyzacji, aby skojarzyć klasę ISupportErrorInfo
implementacji z mechanizmem MFC QueryInterface
. Instrukcja INTERFACE_PART
pasuje do klasy zdefiniowanej dla ISupportErrorInfo
elementu .
Na koniec zaimplementuj klasę zdefiniowaną w celu obsługi ISupportErrorInfo
klasy .
(Przykład acDUAL zawiera trzy makra ułatwiające wykonanie tych trzech kroków, DECLARE_DUAL_ERRORINFO
, DUAL_ERRORINFO_PART
i IMPLEMENT_DUAL_ERRORINFO
, wszystkie zawarte w MFCDUAL.H.
Poniższy przykład implementuje klasę zdefiniowaną w celu obsługi ISupportErrorInfo
klasy . CAutoClickDoc
jest nazwą klasy automatyzacji i IID_IDualAClick
jest identyfikatorem IID interfejsu, który jest źródłem błędów zgłaszanych za pośrednictwem obiektu błędu automatyzacji 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;
}
Zobacz też
Uwagi techniczne według numerów
Uwagi techniczne według kategorii