Udostępnij za pośrednictwem


TN038: implementacja interfejsu MFC/OLE IUnknown

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 centrum OLE 2 jest "Model obiektów składników OLE" lub COM. Com definiuje standard sposobu komunikowania się ze sobą współpracujących obiektów. Obejmuje to szczegóły wyglądu "obiektu", w tym sposób wysyłania metod do obiektu. Com definiuje również klasę bazową, z której pochodzą wszystkie klasy zgodne z com. Ta klasa bazowa to IUnknown. Mimo że interfejs IUnknown jest określany jako klasa C++, com nie jest specyficzny dla żadnego z jednego języka — można go zaimplementować w języku C, PASCAL lub innym języku, który może obsługiwać układ binarny obiektu COM.

Obiekt OLE odnosi się do wszystkich klas pochodzących z elementu IUnknown jako "interfejsy". Jest to ważne rozróżnienie, ponieważ "interfejs", taki jak IUnknown , nie niesie ze sobą żadnej implementacji. Po prostu definiuje protokół, za pomocą którego obiekty komunikują się, a nie specyfikę tego, co robią te implementacje. Jest to uzasadnione w przypadku systemu, który zapewnia maksymalną elastyczność. Zadaniem MFC jest zaimplementowanie domyślnego zachowania programów MFC/C++.

Aby zrozumieć implementację interfejsu IUnknown w MFC, musisz najpierw zrozumieć, czym jest ten interfejs. Uproszczona wersja elementu IUnknown jest zdefiniowana poniżej:

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Uwaga

Niektóre niezbędne szczegóły konwencji wywoływania, takie jak __stdcall zostały pominięte na tej ilustracji.

Funkcje składowe AddRef i Release kontrolują zarządzanie pamięcią obiektu. Model COM używa schematu zliczania odwołań, aby śledzić obiekty. Obiekt nigdy nie jest odwoływał się bezpośrednio, tak jak w języku C++. Zamiast tego obiekty COM zawsze odwołują się do wskaźnika. Aby zwolnić obiekt po zakończeniu korzystania z niego przez właściciela, element członkowski wydania obiektu jest wywoływany (w przeciwieństwie do używania usuwania operatora, tak jak w przypadku tradycyjnego obiektu C++). Mechanizm zliczania odwołań umożliwia zarządzanie wieloma odwołaniami do pojedynczego obiektu. Implementacja elementów AddRef i Release utrzymuje liczbę odwołań względem obiektu — obiekt nie zostanie usunięty, dopóki liczba odwołań nie osiągnie zera.

Dodatki AddRef i Release są dość proste z punktu widzenia implementacji. Oto trywialna implementacja:

ULONG CMyObj::AddRef()
{
    return ++m_dwRef;
}

ULONG CMyObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

Funkcja składowa QueryInterface jest nieco bardziej interesująca. Nie jest to bardzo interesujące, aby mieć obiekt, którego jedynymi funkcjami członkowskimi są AddRef i Release — warto powiedzieć obiektowi, aby robił więcej rzeczy niż zapewnia element IUnknown. Jest to miejsce, w którym narzędzie QueryInterface jest przydatne. Umożliwia uzyskanie innego "interfejsu" na tym samym obiekcie. Te interfejsy są zwykle pochodzące z elementu IUnknown i dodają dodatkowe funkcje, dodając nowe funkcje składowe. Interfejsy COM nigdy nie mają zmiennych składowych zadeklarowanych w interfejsie, a wszystkie funkcje składowe są deklarowane jako pure-virtual. Przykład:

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

Aby uzyskać element IPrintInterface, jeśli masz tylko element IUnknown, wywołaj metodę QueryInterface przy użyciu elementu IIDIPrintInterface. Jest IID to 128-bitowa liczba, która jednoznacznie identyfikuje interfejs. Istnieje dla każdego interfejsu IID zdefiniowanego przez Użytkownika lub OLE. Jeśli pUnk jest wskaźnikiem do obiektu IUnknown , kod pobierania z niego elementu IPrintInterface może być:

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();
    // release pointer obtained via QueryInterface
}

Wydaje się to dość proste, ale sposób implementacji obiektu obsługującego zarówno interfejs IPrintInterface, jak i IUnknown w tym przypadku jest prosty, ponieważ interfejs IPrintInterface jest uzyskiwany bezpośrednio z interfejsu IUnknown — implementując interfejs IPrintInterface, IUnknown jest obsługiwany automatycznie. Przykład:

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

Implementacje elementów AddRef i Release byłyby dokładnie takie same jak te zaimplementowane powyżej. CPrintObj::QueryInterface będzie wyglądać mniej więcej tak:

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

Jak widać, jeśli identyfikator interfejsu (IID) jest rozpoznawany, wskaźnik jest zwracany do obiektu; w przeciwnym razie wystąpi błąd. Należy również pamiętać, że pomyślne działanie elementu QueryInterface powoduje dorozumiany element AddRef. Oczywiście należy również zaimplementować CEditObj::P rint. Jest to proste, ponieważ interfejs IPrintInterface został bezpośrednio uzyskany z interfejsu IUnknown . Jeśli jednak chcesz obsługiwać dwa różne interfejsy, oba pochodzą z rozwiązania IUnknown, rozważ następujące kwestie:

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

Chociaż istnieje wiele różnych sposobów implementowania klasy obsługującej zarówno IEditInterface, jak i IPrintInterface, w tym przy użyciu wielu dziedziczenia języka C++, ta uwaga koncentruje się na użyciu klas zagnieżdżonych w celu zaimplementowania tej funkcji.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

Cała implementacja jest uwzględniona poniżej:

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef()
{
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CEditObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

ULONG CEditPrintObj::CPrintObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CPrintObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

Zwróć uwagę, że większość implementacji IUnknown jest umieszczana w klasie CEditPrintObj zamiast duplikować kod w CEditPrintObj::CEditObj i CEditPrintObj::CPrintObj. Zmniejsza to ilość kodu i pozwala uniknąć błędów. Kluczową kwestią jest to, że z interfejsu IUnknown można wywołać element QueryInterface w celu pobrania dowolnego interfejsu, który może obsługiwać obiekt, a z każdego z tych interfejsów można wykonać to samo. Oznacza to, że wszystkie funkcje QueryInterface dostępne z każdego interfejsu muszą zachowywać się dokładnie tak samo. Aby te obiekty osadzone wywoływać implementację w "obiekcie zewnętrznym", używany jest wskaźnik wsteczny (m_pParent). Wskaźnik m_pParent jest inicjowany podczas konstruktora CEditPrintObj. Następnie należy zaimplementować CEditPrintObj::CPrintObj::P rintObject i CEditPrintObj::CEditObj::EditObject. Dodano sporo kodu, aby dodać jedną funkcję — możliwość edytowania obiektu. Na szczęście interfejsy mają tylko jedną funkcję składową (chociaż tak się dzieje), a w tym przypadku obiekty EditObject i PrintObject zwykle są łączone w jeden interfejs.

Jest to wiele wyjaśnień i wiele kodu dla tak prostego scenariusza. Klasy MFC/OLE zapewniają prostszą alternatywę. Implementacja MFC używa techniki podobnej do sposobu, w jaki komunikaty systemu Windows są opakowane za pomocą Mapy komunikatów. Ta funkcja nosi nazwę Interfejs Mapy i została omówiona w następnej sekcji.

Mapy interfejsu MFC

MFC/OLE zawiera implementację "interfejsu Mapy" podobną do "Mapy komunikatów" MFC i "Dispatch Mapy" w koncepcji i wykonaniu. Podstawowe funkcje interfejsu MFC Mapy są następujące:

Ponadto mapy interfejsu obsługują następujące funkcje zaawansowane:

  • Obsługa tworzenia aggregatable obiektów COM

  • Obsługa używania obiektów agregowanych w implementacji obiektu COM

  • Implementacja jest podłączona i rozszerzalna

Aby uzyskać więcej informacji na temat agregacji, zobacz temat Agregacja .

Obsługa mapy interfejsu MFC jest zakorzeniona w CCmdTarget klasie . CCmdTarget Liczba odwołań "has-a" oraz wszystkie funkcje składowe skojarzone z implementacją IUnknown (liczba odwołań na przykład znajduje się w elemencie CCmdTarget). Aby utworzyć klasę, która obsługuje interfejs OLE COM, należy utworzyć klasę na podstawie CCmdTarget różnych makr i używać różnych makr, a także funkcji CCmdTarget składowych w celu zaimplementowania żądanych interfejsów. Implementacja MFC używa klas zagnieżdżonych do definiowania każdej implementacji interfejsu podobnie jak w powyższym przykładzie. Jest to łatwiejsze dzięki standardowej implementacji funkcji IUnknown, a także wielu makr, które eliminują niektóre powtarzające się kod.

Podstawy mapy interfejsu

Aby zaimplementować klasę przy użyciu map interfejsu MFC

  1. Utwórz klasę bezpośrednio lub pośrednio z CCmdTargetklasy .

  2. DECLARE_INTERFACE_MAP Użyj funkcji w definicji klasy pochodnej.

  3. Dla każdego interfejsu, który chcesz obsługiwać, użyj makr BEGIN_INTERFACE_PART i END_INTERFACE_PART w definicji klasy.

  4. W pliku implementacji użyj makr BEGIN_INTERFACE_MAP i END_INTERFACE_MAP, aby zdefiniować mapę interfejsu klasy.

  5. Dla każdego obsługiwanego identyfikatora IID użyj makra INTERFACE_PART między makrami BEGIN_INTERFACE_MAP i END_INTERFACE_MAP, aby zamapować ten identyfikator na określoną "część" klasy.

  6. Zaimplementuj wszystkie zagnieżdżone klasy reprezentujące obsługiwane interfejsy.

  7. Użyj makra METHOD_PROLOGUE, aby uzyskać dostęp do nadrzędnego obiektu pochodnego CCmdTarget.

  8. Funkcje AddRef, Release i QueryInterface mogą delegować do CCmdTarget implementacji tych funkcji (ExternalAddRef, ExternalReleasei ExternalQueryInterface).

Powyższy przykład CPrintEditObj można zaimplementować w następujący sposób:

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

Powyższa deklaracja tworzy klasę pochodzącą z CCmdTargetklasy . Makro DECLARE_INTERFACE_MAP informuje platformę, że ta klasa będzie miała niestandardową mapę interfejsu. Ponadto makra BEGIN_INTERFACE_PART i END_INTERFACE_PART definiują klasy zagnieżdżone, w tym przypadku z nazwami CEditObj i CPrintObj (X jest używany tylko do odróżnienia zagnieżdżonych klas od klas globalnych rozpoczynających się od "C" i klas interfejsu rozpoczynających się od "I"). Tworzone są dwa zagnieżdżone elementy członkowskie tych klas: m_CEditObj i m_CPrintObj. Makra automatycznie deklarują funkcje AddRef, Release i QueryInterface, dlatego deklarujesz tylko funkcje specyficzne dla tego interfejsu: EditObject i PrintObject (używany jest makro OLE STDMETHOD, aby _stdcall i słowa kluczowe wirtualne były udostępniane odpowiednio dla platformy docelowej).

Aby zaimplementować mapę interfejsu dla tej klasy:

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

Spowoduje to połączenie identyfikatora IID IID_IPrintInterface z m_CPrintObj i IID_IEditInterface odpowiednio z m_CEditObj. Implementacja CCmdTargetqueryInterface (CCmdTarget::ExternalQueryInterface) używa tej mapy do zwracania wskaźników do m_CPrintObj i m_CEditObj po zażądaniu. Nie jest konieczne dołączenie wpisu dla IID_IUnknownprogramu ; platforma będzie używać pierwszego interfejsu na mapie (w tym przypadku m_CPrintObj), gdy IID_IUnknown jest wymagane.

Mimo że makro BEGIN_INTERFACE_PART automatycznie zadeklarowało funkcje AddRef, Release i QueryInterface , nadal trzeba je zaimplementować:

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid,
    void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

Implementacja CEditPrintObj::CPrintObj będzie podobna do powyższych definicji CEditPrintObj::CEditObj. Chociaż można utworzyć makro, które może służyć do automatycznego generowania tych funkcji (ale wcześniej w programowania MFC/OLE było to możliwe), trudno jest ustawić punkty przerwania, gdy makro generuje więcej niż jeden wiersz kodu. Z tego powodu ten kod jest rozszerzany ręcznie.

Korzystając ze struktury implementacji map komunikatów, istnieje wiele rzeczy, które nie były konieczne do wykonania:

  • Implementowanie zapytaniaInterface

  • Implementowanie elementu AddRef i wydania

  • Zadeklaruj jedną z tych wbudowanych metod w obu interfejsach

Ponadto struktura używa map komunikatów wewnętrznie. Dzięki temu można pochodzić z klasy struktury, np COleServerDoc. , która już obsługuje niektóre interfejsy i zapewnia zamiany lub dodatki do interfejsów udostępnianych przez platformę. Można to zrobić, ponieważ struktura w pełni obsługuje dziedziczenie mapy interfejsu z klasy bazowej. Dlatego BEGIN_INTERFACE_MAP przyjmuje jako drugi parametr nazwę klasy bazowej.

Uwaga

Zazwyczaj nie można ponownie użyć implementacji wbudowanych implementacji interfejsów OLE MFC, dziedzicząc osadzoną specjalizację tego interfejsu z wersji MFC. Nie jest to możliwe, ponieważ użycie makra METHOD_PROLOGUE w celu uzyskania dostępu do zawierającego CCmdTargetobiektu pochodnego oznacza stałe przesunięcie obiektu osadzonego z -pochodnego CCmdTargetobiektu. Oznacza to na przykład, że nie można utworzyć osadzonego elementu XMyAdviseSink z implementacji MFC w systemie COleClientItem::XAdviseSink, ponieważ XAdviseSink opiera się na określonym przesunięciu od góry COleClientItem obiektu.

Uwaga

Możesz jednak delegować do implementacji MFC dla wszystkich funkcji, które mają być domyślne zachowanie MFC. Odbywa się to w implementacji IOleInPlaceFrame MFC (XOleInPlaceFrame) w COleFrameHook klasie (deleguje do m_xOleInPlaceUIWindow dla wielu funkcji). Ten projekt został wybrany w celu zmniejszenia rozmiaru środowiska uruchomieniowego obiektów, które implementują wiele interfejsów; eliminuje potrzebę użycia wskaźnika wstecznego (na przykład sposobu, w jaki m_pParent był używany w poprzedniej sekcji).

Mapy agregacji i interfejsu

Oprócz obsługi autonomicznych obiektów COM, MFC obsługuje również agregację. Sama agregacja jest zbyt złożona, aby omówić tutaj; Aby uzyskać więcej informacji na temat agregacji, zapoznaj się z tematem Agregacja . Ta uwaga opisuje po prostu obsługę agregacji wbudowanej w strukturę i mapy interfejsu.

Istnieją dwa sposoby używania agregacji: (1) przy użyciu obiektu COM, który obsługuje agregację, i (2) implementowanie obiektu, który może być agregowany przez inny. Te możliwości mogą być określane jako "używanie obiektu agregowanego" i "tworzenie obiektu agregowalnego". Protokół MFC obsługuje oba te elementy.

Używanie obiektu agregowanego

Aby użyć obiektu agregowanego, należy powiązać agregację z mechanizmem QueryInterface. Innymi słowy, obiekt agregowany musi zachowywać się tak, jakby był natywną częścią obiektu. W jaki sposób jest to związane z mechanizmem mapy interfejsu MFC Oprócz makra INTERFACE_PART, w którym zagnieżdżony obiekt jest mapowany na identyfikator IID, można również zadeklarować obiekt agregacji w ramach klasy pochodnej CCmdTarget . W tym celu jest używane makro INTERFACE_AGGREGATE. Dzięki temu można określić zmienną składową (która musi być wskaźnikiem do klasy IUnknown lub pochodnej), która ma być zintegrowana z mechanizmem mapy interfejsu. Jeśli wskaźnik nie ma wartości NULL podczas CCmdTarget::ExternalQueryInterface wywoływania, struktura automatycznie wywoła funkcję składową QueryInterface obiektu agregowanego, jeśli IID żądany element nie jest jednym z natywnych IIDelementów obsługiwanych przez CCmdTarget sam obiekt.

Aby użyć makra INTERFACE_AGGREGATE

  1. Zadeklaruj zmienną składową (an IUnknown*), która będzie zawierać wskaźnik do obiektu agregowanego.

  2. Uwzględnij makro INTERFACE_AGGREGATE na mapie interfejsu, które odwołuje się do zmiennej składowej według nazwy.

  3. W pewnym momencie (zwykle w trakcie CCmdTarget::OnCreateAggregates) zainicjuj zmienną składową na inną wartość niż NULL.

Przykład:

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);

    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

Zmienna m_lpAggrInner jest inicjowana w konstruktorze na wartość NULL. Struktura ignoruje zmienną składową NULL w domyślnej implementacji elementu QueryInterface. OnCreateAggregates to dobre miejsce do faktycznego utworzenia obiektów agregacji. Należy go jawnie wywołać, jeśli tworzysz obiekt poza implementacją MFC klasy COleObjectFactory. Przyczyna tworzenia agregacji w CCmdTarget::OnCreateAggregates obiekcie oraz użycie CCmdTarget::GetControllingUnknown elementu stanie się widoczna podczas tworzenia obiektów aggregatable.

Ta technika zapewni obiektowi wszystkie interfejsy obsługiwane przez obiekt agregujący oraz jego interfejsy natywne. Jeśli chcesz tylko podzbiór interfejsów, które obsługuje agregacja, możesz zastąpić CCmdTarget::GetInterfaceHook. Dzięki temu można bardzo niskopoziomowe łączenie, podobnie jak w przypadku elementu QueryInterface. Zwykle potrzebujesz wszystkich interfejsów, które obsługuje agregacja.

Tworzenie aggregatable implementacji obiektu

Aby obiekt był aggregatable, implementacja elementu AddRef, Release i QueryInterface musi delegować do "kontrolującego nieznanego". Innymi słowy, aby był częścią obiektu, musi delegować element AddRef, Release i QueryInterface do innego obiektu, również pochodzącego z elementu IUnknown. Ten element "kontrolujący nieznany" jest dostarczany do obiektu podczas jego tworzenia, czyli jest dostarczany do implementacji .COleObjectFactory Zaimplementowanie tej metody wiąże się z niewielkim obciążeniem, a w niektórych przypadkach nie jest pożądane, więc MFC sprawia, że jest to opcjonalne. Aby umożliwić aggregatable obiekt, należy wywołać CCmdTarget::EnableAggregation metodę z konstruktora obiektu.

Jeśli obiekt używa również agregacji, należy również przekazać poprawne "kontrolowanie nieznane" do obiektów agregujących. Zazwyczaj ten wskaźnik IUnknown jest przekazywany do obiektu podczas tworzenia agregacji. Na przykład parametr pUnkOuter to "kontrolowanie nieznanych" obiektów utworzonych za pomocą CoCreateInstancepolecenia . Prawidłowy wskaźnik "kontrolowanie nieznany" można pobrać, wywołując polecenie CCmdTarget::GetControllingUnknown. Wartość zwrócona z tej funkcji jest jednak nieprawidłowa podczas konstruktora. Z tego powodu zaleca się utworzenie agregacji tylko w przesłonięcie CCmdTarget::OnCreateAggregateswartości , gdzie wartość zwracana z GetControllingUnknown jest niezawodna, nawet jeśli została utworzona na podstawie COleObjectFactory implementacji.

Ważne jest również, aby obiekt manipulował prawidłową liczbą odwołań podczas dodawania lub zwalniania sztucznych liczb odwołań. Aby upewnić się, że tak jest, zawsze należy wywołać ExternalAddRef polecenie i ExternalRelease zamiast InternalRelease i InternalAddRef. Rzadko jest wywoływanie InternalRelease lub InternalAddRef w klasie obsługującej agregację.

Materiał referencyjny

Zaawansowane użycie ole, takie jak definiowanie własnych interfejsów lub zastępowanie implementacji interfejsów OLE platformy wymaga użycia podstawowego mechanizmu mapy interfejsu.

W tej sekcji omówiono poszczególne makra i interfejsy API, które są używane do implementowania tych zaawansowanych funkcji.

CCmdTarget::EnableAggregation — opis funkcji

void EnableAggregation();

Uwagi

Wywołaj tę funkcję w konstruktorze klasy pochodnej, jeśli chcesz obsługiwać agregację OLE dla obiektów tego typu. Spowoduje to przygotowanie specjalnej implementacji elementu IUnknown wymaganej do aggregatable obiektów.

CCmdTarget::ExternalQueryInterface — opis funkcji

DWORD ExternalQueryInterface(
    const void FAR* lpIID,
    LPVOIDFAR* ppvObj
);

Parametry

Identyfikator lpIID
Wskaźnik daleko do identyfikatora IID (pierwszy argument zapytaniaInterface)

ppvObj
Wskaźnik do elementu IUnknown* (drugi argument zapytaniaInterface)

Uwagi

Wywołaj tę funkcję w implementacji elementu IUnknown dla każdego interfejsu, który implementuje klasa. Ta funkcja zapewnia standardową implementację opartą na danych elementu QueryInterface opartą na mapie interfejsu obiektu. Konieczne jest rzutowanie wartości zwracanej na wartość HRESULT. Jeśli obiekt jest zagregowany, ta funkcja wywoła metodę "kontrolowanie elementu IUnknown" zamiast używać mapy interfejsu lokalnego.

CCmdTarget::ExternalAddRef — opis funkcji

DWORD ExternalAddRef();

Uwagi

Wywołaj tę funkcję w implementacji elementu IUnknown::AddRef dla każdego interfejsu implementuje klasę. Wartość zwracana to nowa liczba odwołań dla obiektu CCmdTarget. Jeśli obiekt jest agregowany, ta funkcja wywoła funkcję "kontrolowanie elementu IUnknown" zamiast manipulować lokalną liczbą odwołań.

CCmdTarget::ExternalRelease — Opis funkcji

DWORD ExternalRelease();

Uwagi

Wywołaj tę funkcję w implementacji elementu IUnknown::Release dla każdego interfejsu implementowania klasy. Wartość zwracana wskazuje nową liczbę odwołań dla obiektu. Jeśli obiekt jest agregowany, ta funkcja wywoła funkcję "kontrolowanie elementu IUnknown" zamiast manipulować lokalną liczbą odwołań.

DECLARE_INTERFACE_MAP — opis makra

DECLARE_INTERFACE_MAP

Uwagi

Użyj tego makra w dowolnej klasie pochodzącej z CCmdTarget tej klasy, która będzie mieć mapę interfejsu. Używane w taki sam sposób jak DECLARE_MESSAGE_MAP. To wywołanie makra należy umieścić w definicji klasy, zwykle w nagłówku (. Plik H). Klasa z DECLARE_INTERFACE_MAP musi zdefiniować mapę interfejsu w pliku implementacji (. CPP) z makrami BEGIN_INTERFACE_MAP i END_INTERFACE_MAP.

BEGIN_INTERFACE_PART i END_INTERFACE_PART — opisy makr

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

Parametry

localClass
Nazwa klasy, która implementuje interfejs

iface
Nazwa interfejsu implementowania tej klasy

Uwagi

Dla każdego interfejsu, który będzie implementować klasa, musisz mieć parę BEGIN_INTERFACE_PART i END_INTERFACE_PART. Te makra definiują klasę lokalną pochodzącą z zdefiniowanego interfejsu OLE, a także osadzoną zmienną składową tej klasy. Elementy członkowskie AddRef, Release i QueryInterface są deklarowane automatycznie. Należy dołączyć deklaracje dla innych funkcji składowych, które są częścią implementowanych interfejsu (deklaracje te są umieszczane między BEGIN_INTERFACE_PART i makrami END_INTERFACE_PART).

Argument iface to interfejs OLE, który chcesz zaimplementować, taki jak IAdviseSink, lub IPersistStorage (lub własny interfejs niestandardowy).

Argument localClass to nazwa klasy lokalnej, która zostanie zdefiniowana. Znak "X" zostanie automatycznie wstawiony do nazwy. Ta konwencja nazewnictwa służy do unikania kolizji z klasami globalnymi o tej samej nazwie. Ponadto nazwa osadzonego elementu członkowskiego, taka sama jak nazwa localClass , z wyjątkiem tego, że jest poprzedzona prefiksem "m_x".

Przykład:

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
    STDMETHOD_(void, OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
    STDMETHOD_(void, OnViewChange)(DWORD, LONG);
    STDMETHOD_(void, OnRename)(LPMONIKER);
    STDMETHOD_(void, OnSave)();
    STDMETHOD_(void, OnClose)();
END_INTERFACE_PART(MyAdviseSink)

zdefiniuj klasę lokalną o nazwie XMyAdviseSink pochodzącą z IAdviseSink, a element członkowski klasy, w której jest deklarowany o nazwie m_xMyAdviseSink.Uwaga:

Uwaga

Wiersze rozpoczynające się od STDMETHOD_ są zasadniczo kopiowane z OLE2. H i nieznacznie zmodyfikowany. Kopiowanie ich z OLE2. H może zmniejszyć błędy, które trudno rozwiązać.

BEGIN_INTERFACE_MAP i END_INTERFACE_MAP — opisy makr

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

Parametry

theClass
Klasa, w której ma zostać zdefiniowana mapa interfejsu

Baseclass
Klasa, z której pochodzi klasa TheClass .

Uwagi

Makra BEGIN_INTERFACE_MAP i END_INTERFACE_MAP są używane w pliku implementacji do faktycznego zdefiniowania mapy interfejsu. Dla każdego zaimplementowanego interfejsu istnieje co najmniej jedno wywołanie makra INTERFACE_PART. Dla każdej agregacji używanej przez klasę istnieje jedno wywołanie makra INTERFACE_AGGREGATE.

INTERFACE_PART — opis makra

INTERFACE_PART(theClass, iid, localClass)

Parametry

theClass
Nazwa klasy zawierającej mapę interfejsu.

Iid
Element IID , który ma zostać zamapowany na klasę osadzoną.

localClass
Nazwa klasy lokalnej (mniej "X").

Uwagi

To makro jest używane między makrem BEGIN_INTERFACE_MAP a makrem END_INTERFACE_MAP dla każdego interfejsu obsługiwanego przez obiekt. Umożliwia mapowania identyfikatora IID na składową klasy wskazanej przez klasęClass i localClass. Element "m_x" zostanie automatycznie dodany do klasy localClass . Należy pamiętać, że więcej niż jeden element IID członkowski może być skojarzony z jednym elementem członkowskim. Jest to bardzo przydatne, gdy implementujesz tylko interfejs "najbardziej pochodny" i chcesz również udostępnić wszystkie interfejsy pośrednie. Dobrym przykładem tego jest IOleInPlaceFrameWindow interfejs. Jego hierarchia wygląda następująco:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Jeśli obiekt implementuje IOleInPlaceFrameWindowelement , klient może QueryInterface mieć dowolny z tych interfejsów: IOleUIWindow, IOleWindowlub IUnknown, oprócz interfejsu IOleInPlaceFrameWindow "najbardziej pochodnego" (ten, który faktycznie implementujesz). Aby to zrobić, można użyć więcej niż jednego makra INTERFACE_PART do mapowania każdego interfejsu podstawowego IOleInPlaceFrameWindow na interfejs:

w pliku definicji klasy:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

w pliku implementacji klasy:

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

Struktura zajmuje się elementem IUnknown, ponieważ jest ona zawsze wymagana.

INTERFACE_PART — opis makra

INTERFACE_AGGREGATE(theClass, theAggr)

Parametry

theClass
Nazwa klasy, która zawiera mapę interfejsu,

theAggr
Nazwa zmiennej składowej, która ma być agregowana.

Uwagi

To makro służy do określania struktury, że klasa używa obiektu agregowanego. Musi występować między makrami BEGIN_INTERFACE_PART i END_INTERFACE_PART. Obiekt agregacji jest oddzielnym obiektem pochodzącym z elementu IUnknown. Za pomocą agregacji i makra INTERFACE_AGGREGATE można ustawić wszystkie interfejsy obsługiwane przez agregację, które są bezpośrednio obsługiwane przez obiekt. ArgumentAggr jest po prostu nazwą zmiennej składowej klasy, która pochodzi z elementu IUnknown (bezpośrednio lub pośrednio). Wszystkie makra INTERFACE_AGGREGATE muszą być zgodne z makrami INTERFACE_PART po umieszczeniu ich na mapie interfejsu.

Zobacz też

Uwagi techniczne według numerów
Uwagi techniczne według kategorii