Udostępnij za pośrednictwem


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 IDispatchw 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 IDispatchklasy . Najprostszym sposobem zaimplementowania obsługi dwóch interfejsów w klasie opartej CCmdTargetna 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 IDispatchklasy . 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 metody COleObjectFactory::UpdateRegistryAll. Zgodnie z tym wywołaniem dodaj wywołanie metody , AfxOleRegisterTypeLibokreś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:

  1. W menu Kompilacja kliknij Ustawienia, a następnie wybierz plik ODL z listy plików dla każdej konfiguracji.

  2. 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:

  1. Uwzględnij plik nagłówka wygenerowany przez bibliotekę MkTypLib w standardzie zawierający plik nagłówka stdafx.h.

  2. 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"
    
  3. W menu Kompilacja kliknij pozycję Ustawienia, a następnie wybierz pozycję INITIIDS. Program CPP z listy plików dla każdej konfiguracji.

  4. 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, DualHandleExceptionznajduje 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 przez HRESULTsystem.

  • 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 ISupportErrorInfoelementu .

Na koniec zaimplementuj klasę zdefiniowaną w celu obsługi ISupportErrorInfoklasy .

(Przykład acDUAL zawiera trzy makra ułatwiające wykonanie tych trzech kroków, DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PARTi IMPLEMENT_DUAL_ERRORINFO, wszystkie zawarte w MFCDUAL.H.

Poniższy przykład implementuje klasę zdefiniowaną w celu obsługi ISupportErrorInfoklasy . 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