TN038: implementacja interfejsu MFC/OLE IUnknown
[!UWAGA]
Następująca uwaga techniczna nie został zaktualizowana od pierwszego uwzględnienia jej w dokumentacji online.W rezultacie niektóre procedury i tematy może być nieaktualne lub nieprawidłowe.Aby uzyskać najnowsze informacje, zaleca się wyszukać temat w indeksie dokumentacji online.
Istotą OLE 2 jest „OLE Component Object Model” lub model COM.W modelu COM zdefiniowano standard dla obiektów współpracujących, jak mają komunikować się ze sobą.Obejmuje to szczegółowe informacje o wyglądzie "obiektu", łącznie z informacjami o sposobach wysyłania do obiektu metod.COM definiuje również klasę podstawową, z której pochodzą wszystkie zgodne klasy COM.Ta klasa podstawowa to IUnknown.Chociaż interfejs IUnknown jest określany w odniesieniu do języka C++, COM nie jest specyficzny dla żadnego poszczególnego języka — może to być realizowane w języku C, PASCAL lub w innym, który może obsługiwać układ binarny obiektu COM.
W mechanizmie OLE wszystkie klasy pochodne od obiektu IUnknown są nazywane „interfejsami”. Jest to istotna różnica, gdyż "interfejs" taki jak IUnknown nie niesie ze sobą implementacji.Po prostu definiuje protokół, przez który komunikują się obiekty, a nie szczegóły tego, co zrobią te implementacje.Ma to uzasadnienie dla systemu, który pozwala na maksymalną elastyczność.To jest zadanie biblioteki MFC, aby zaimplementować domyślne zachowanie dla programów MFC/C++.
Aby zrozumieć implementację MFC IUnknown musisz najpierw zrozumieć, czym jest ten interfejs.Uproszczoną wersję IUnknown określono 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 pozostawiono na tej ilustracji.
Funkcje elementów członkowskich AddRef i Wydanie kontrolują zarządzanie pamięcią obiektu.COM używa schematu obliczeń odwołania do śledzenia obiektów.Nigdy nie odwołuje się do obiektu bezpośrednio, tak jak w języku C++.Zamiast tego obiekty COM zawsze są wywoływane za pomocą wskaźnika.Aby zwolnić obiekt, gdy właściciel przestanie go używać, wywoływany jest członek Zwolnij obiektu (w przeciwieństwie do używania operatora Usuń, jak to by miało miejsce w przypadku tradycyjnego obiektu języka C++).Mechanizm zliczania odwołań zezwala na zarządzanie wieloma odwołaniami do jednego obiektu.Implementacja AddRef i Release przechowuje licznik odwołań do obiektu — obiekt nie zostanie usunięty, dopóki jego licznik odwołań nie osiągnie zera.
AddRef i Zwolnij działa dość prosto z punktu widzenia wdrażania.Oto prosta implementacja:
ULONG CMyObj::AddRef()
{
return ++m_dwRef;
}
ULONG CMyObj::Release()
{
if (--m_dwRef == 0)
{
delete this;
return 0;
}
return m_dwRef;
}
Funkcja członkowska QueryInterface jest nieco bardziej interesująca.Nie jest bardzo interesujące, aby mieć obiekt, którego jedynymi funkcjami członkowskimi są AddRef i Release — dobrze byłoby powiedzieć obiektowi, aby zrobił coś więcej niż zapewnia interfejs IUnknown.To jest, gdy QueryInterface jest przydatny.Umożliwia uzyskanie innego „interfejsu” dla tego samego obiektu.Te interfejsy są zazwyczaj uzyskiwane z IUnknown i dodają dodatkową funkcję, dodając nowe funkcje członkowskie.Interfejsy COM nigdy nie mają zmiennych zadeklarowanych w interfejsie, a wszystkie funkcje składowe są deklarowane jako czysto wirtualne.Na przykład:
class IPrintInterface : public IUnknown
{
public:
virtual void PrintObject() = 0;
};
Aby uzyskać IPrintInterface, jeśli masz tylko IUnknown, wywołaj QueryInterface za pomocą IID z IPrintInterface.IID jest 128-bitową liczbą, która jednoznacznie identyfikuje interfejs.Istnieje IID dla każdego interfejsu, który definiować może użytkownik lub OLE.Jeśli parametr pUnk jest wskaźnikiem do obiektu IUnknown, kod, aby pobrać obiekt IPrintInterface z niego, 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 jak zaimplementujesz obiekt, który obsługuje zarówno interfejs IPrintInterface, jak i IUnknown?W tym przypadku jest to proste, ponieważ interfejs IPrintInterface pochodzi bezpośrednio z interfejsu IUnknown — implementując interfejs IPrintInterface, interfejs IUnknown jest obsługiwana automatycznie.Na przykład:
class CPrintObj : public CPrintInterface
{
virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
virtual ULONG AddRef();
virtual ULONG Release();
virtual void PrintObject();
};
Implementacje AddRef i Wydanie będą dokładnie takie same, jak te implementowane 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ż zauważyć, że udane wyniki QueryInterface skutkują domniemanymi AddRef.Oczywiście będzie również musiał zostać zaimplementowany obiekt CEditObj::Print.To proste, ponieważ IPrintInterface pochodzi bezpośrednio od interfejsu IUnknown.Jednakże jeśli chce się obsługiwać dwa różne interfejsy, oba pochodzące z obiektu IUnknown, należy uwzględnić następujące uwagi:
class IEditInterface : public IUnkown
{
public:
virtual void EditObject() = 0;
};
Chociaż istnieje wiele różnych sposobów wdrażania klasy pomocniczej zarówno IEditInterface, jak i IPrintInterface, w tym przy użyciu dziedziczenia wielokrotnego języka C++, ta notatka 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 znajduje się 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);
}
Należy zauważyć, że większość implementacji interfejsu IUnknown jest umieszczana w klasie CEditPrintObj, a nie powiela kod w obiektach CEditPrintObj::CEditObj i CEditPrintObj::CPrintObj.Zmniejsza to ilość kodu i pozwala uniknąć błędów.Kluczowym punktem tutaj jest fakt, że z interfejsu IUnknown można wywołać QueryInterface, aby pobrać dowolny interfejs, jaki może być obsługiwany przez obiekt, i z każdego z tych interfejsów można zrobić 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ły implementację w „obiekcie zewnętrznym”, jest używany wskaźnik zwrotny (m_pParent).Wskaźnik m_pParent jest inicjowany podczas konstruktora CEditPrintObj.Następnie możesz również zaimplementować CEditPrintObj::CEditObj::EditObject i CEditPrintObj::CPrintObj::PrintObject.Spora ilość kodu została dodana w celu dodania jednej funkcji — możliwość edycji obiektu.Na szczęście dość rzadko interfejsy mają tylko pojedynczą funkcję członkowską (choć zdarza się to), a w tym przypadku funkcje EditObject i PrintObject byłyby zwykle połączone w pojedynczy interfejs.
To dużo wyjaśnień i duża ilość kodu dla tak prostego scenariusza.Klasy MFC/OLE zapewniają prostszą alternatywę.Implementacja MFC wykorzystuje technikę podobną to sposobu, w jaki wiadomości systemu Windows są zawijane z Mapami wiadomości.Ten obiekt jest nazywany Mapy interfejs i został opisany w następnym rozdziale.
Mapy interfejsów biblioteki MFC
Mechanizm MFC/OLE zawiera implementację „map interfejsów” podobną pod względem koncepcji i wykonywania do „map komunikatów” i „map wysyłania” w bibliotece MFC.Podstawowe funkcje Map interfejsu MFC są następujące:
Standardowa implementacja IUnknown, zbudowana w klasie CCmdTarget.
Obsługa licznika odwołań zmodyfikowanego przez funkcje AddRef i Release
Implementacja danych QueryInterface
Ponadto mapy interfejsu obsługują następujące zaawansowane funkcje:
Obsługa tworzenia kumulowane obiektów COM
Wsparcie dla korzystania z obiektów agregacji podczas implementacji obiektu COM
Implementacja ma możliwość bycia wywoływaną i jest rozszerzalna
Aby uzyskać więcej informacji na temat agregacji, zobacz temat Agregacja.
Obsługa mapy interfejsu biblioteki MFC jest osadzona w klasie CCmdTarget.Odwołanie CCmdTarget"has-a" liczy się tak jak wszystkie funkcje składowe związane z implementacją IUnknown (liczba odwołań na przykład znajduje się w CCmdTarget).Aby utworzyć klasę, która obsługuje OLE COM, tworzysz pochodną klasy z CCmdTarget i korzystać z różnych makr oraz funkcji członkowskich z CCmdTarget, aby zaimplementować pożądane interfejsy.Implementacja biblioteki MFC używa klas zagnieżdżonych do definiowania każdej implementacji interfejsu, podobnie jak w powyższym przykładzie.Stało się łatwiejsze dzięki standardowej implementacji IUnknown, a także wielu makr, które eliminują część powtarzającego się kodu.
Aby zaimplementować klasę przy użyciu map interfejsu MFC
Wyprowadź klasę bezpośrednio lub pośrednio z CCmdTarget.
Użyj funkcji DECLARE_INTERFACE_MAP w definicji klasy pochodnej.
Dla każdego interfejsu, który ma być obsługiwany, użyj makr BEGIN_INTERFACE_PART i END_INTERFACE_PART w definicji klasy.
W pliku implementacji użyj makr BEGIN_INTERFACE_MAP i END_INTERFACE_MAP, aby zdefiniować mapę interfejsu klasy.
Dla każdego obsługiwanego identyfikatora IID użyj makra INTERFACE_PART między makrami BEGIN_INTERFACE_MAP i END_INTERFACE_MAP do zmapowania tego identyfikatora IID na określoną „część” klasy.
Zaimplementuj każdą z klas zagnieżdżonych, która reprezentuje interfejsy, które obsługujesz.
Użyj makro METHOD_PROLOGUE, aby uzyskać dostęp do obiektu nadrzędnego, pochodnego CCmdTarget.
AddRef, Zwolnij i QueryInterface można delegować do implementacji CCmdTarget tych funkcji (ExternalAddRef, ExternalRelease i ExternalQueryInterface).
Powyższy przykład CPrintEditObj może być wdrażany 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ę wywodzącą się od CCmdTarget.Makro DECLARE_INTERFACE_MAP informuje szablon, że ta klasa posiadać będzie mapę interfejsu niestandardowego.Dodatkowo makra BEGIN_INTERFACE_PART i END_INTERFACE_PART definiują klasy zagnieżdżone, w tym przypadku o nazwach CEditObj i CPrintObj (litera X jest używana tylko do odróżnienia klas zagnieżdżonych od klas globalnych, które zaczynają się literą „C”, i klas interfejsów, które rozpoczynają się literą „I”).Tworzone są dwa elementy zagnieżdżone tych klas: m_CEditObj i m_CPrintObj, odpowiednio.Makra automatycznie deklarują funkcje AddRef, Wersja, i QueryInterface; dlatego należy deklarować tylko funkcje specyficzne dla tego interfejsu: EditObject i PrintObject (makro OLE STDMETHOD jest używane, aby _stdcall i wirtualne słowa kluczowe były dostarczane jako właściwe 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()
Łączy to IID_IPrintInterface IID z m_CPrintObj a IID_IEditInterface z m_CEditObj.CCmdTarget Wdrażanie QueryInterface (CCmdTarget::ExternalQueryInterface) używa tej mapy do zwracania wskaźników do m_CPrintObj i m_CEditObj, gdy wymagane.Nie jest konieczne dołączyć wpis dla interfejsu IID_IUnknown; środowisko użyje pierwszego interfejsu w mapie (w tym przypadku m_CPrintObj), kiedy zostanie zażądany interfejs IID_IUnknown.
Nawet jeśli makro BEGIN_INTERFACE_PART automatycznie deklaruje funkcje AddRef, Release i QueryInterface, wciąż 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 dla CEditPrintObj::CPrintObj, będzie podobna do powyższych definicji dla CEditPrintObj::CEditObj.Chociaż można utworzyć makro, którego można użyć do automatycznego generowania tych funkcji (ale wcześniej w przypadku rozwoju MFC/OLE), trudno będzie ustawić punkty przerwań, gdy makro generuje więcej niż jeden wiersz kodu.Z tego powodu ten kod jest rozwinięty ręcznie.
Dzięki implementacji środowiska mapy wiadomości jest kilka rzeczy, których nie trzeba będzie robić:
Implementacja funkcji QueryInterface
Implementacja funkcji AddRef i Release
Zadeklaruj jedną z tych metod wbudowanych lub obie dla swoich interfejsów
Ponadto środowisko wykorzystuje mapy komunikatów wewnętrznie.Pozwala to na pochodzenie od klasy framework, powiedzmy COleServerDoc, która już obsługuje niektóre interfejsy oraz udostępnia zamiany lub dodatki do interfejsów dostarczanych przez szablon.Można to zrobić, ponieważ szablon w pełni obsługuje dziedziczenie mapy interfejsu z klasy bazowej.To jest powód, dlaczego BEGIN_INTERFACE_MAP przyjmuje za drugi parametr nazwę klasy podstawowej.
[!UWAGA]
Ogólnie nie jest możliwe ponowne wykorzystanie implementacji interfejsów OLE wbudowanych w bibliotece MFC tylko przez dziedziczenie osadzonych specjalizacji tego interfejsu z wersji biblioteki MFC.Nie jest to możliwe ponieważ użycie makro METHOD_PROLOGUE, aby uzyskać dostęp do zawartego obiektu będącego pochodną CCmdTarget oznacza stałe przesunięcie obiektu osadzonego w stosunku do obiektu będącego pochodną CCmdTarget.Oznacza to, że nie możesz wywodzić osadzonego XMyAdviseSink z implementacji MFC w COleClientItem::XAdviseSink, ponieważ XAdviseSink opiera się na byciu w szczególnym przesunięciu od góry obiektu COleClientItem.
[!UWAGA]
Można delegować do implementacji MFC dla wszystkich funkcji, dla których chcesz, żeby MFC zachowywało się domyślnie.Odbywa się to podczas implementacji MFC IOleInPlaceFrame (XOleInPlaceFrame) w klasie COleFrameHook (przekazuje do m_xOleInPlaceUIWindow dla wielu funkcji).Ten projekt został wybrany do zmniejszenia środowiska uruchomieniowego obiektów, w których zaimplementowano wiele interfejsów; eliminuje to potrzebę zapewnienia wskaźnika wstecz (tak jak sposób, w jaki m_pParent został użyty w poprzedniej sekcji).
Agregacja i mapy interfejsu
Oprócz obsługi autonomicznych obiektów COM biblioteka MFC obsługuje agregację.Agregacja sama w sobie jest zbyt złożonym tematem do omawiania w tym miejscu; zobacz temat Agregacja, aby uzyskać więcej informacji na temat agregacji.Ta uwaga ta po prostu opisze obsługę agregacji wbudowanej w ramy i mapy interfejsu.
Istnieją dwa sposoby użycia agregacji: (1) za pomocą obiektu COM, który obsługuje agregację i (2) poprzez implementację obiektu, który może być agregowany przez inny.Te możliwości mogą być określane jako "za pomocą obiektu agregacji" i "obiekt skumulowany".Biblioteka MFC obsługuje obie.
Używanie obiektu agregacji
Aby użyć obiektu agregacji, musi być jakiś sposób, aby powiązać agregację z mechanizmem QueryInterface.Innymi słowy obiekt agregacji musi zachowywać się tak, jakby był natywną częścią obiektu.W jaki sposób wiąże się to z mechanizmem mapowania interfejsu MFC?W uzupełnieniu do makra INTERFACE_PART w przypadku, gdy zagnieżdżony obiekt jest mapowany do identyfikatora IID, można również zadeklarować obiekt agregacji jako część klasy pochodnej CCmdTarget.Aby to zrobić, należy użyć makro INTERFACE_AGGREGATE.Pozwala to na określenie zmiennej członkowskiej (która musi być wskaźnikiem do IUnknown lub wywodzącej się klasy), która ma zostać włączona do mechanizmu mapy interfejsu.Jeżeli wskaźnik nie jest NULL, gdy jest wywoływana funkcja CCmdTarget::ExternalQueryInterface, środowisko będzie automatycznie wywoływać funkcję członkowską QueryInterface obiektu agregacji, jeśli żądany obiekt IID nie jest jednym z natywnych obiektów IID obsługiwanych przez sam obiekt CCmdTarget.
Aby użyć makra INTERFACE_AGGREGATE
Zadeklaruj zmienną członkowską (IUnknown*), która zawiera wskaźnik do obiektu agregacji.
Umieść w mapie interfejsu makro INTERFACE_AGGREGATE, które odnosi się do zmiennej elementu członkowskiego według nazwy.
W pewnym momencie (zwykle w ciągu CCmdTarget::OnCreateAggregates), zainicjować zmienną członkowską na coś innego niż NULL.
Na 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 w wartości NULL.Szablon ignoruje zmienną członkowską NULL w domyślnym wdrożeniu QueryInterface.Obiekt OnCreateAggregates jest dobrym miejscem do faktycznie tworzenia obiektów agregacji.Musisz wywołać ją jawnie, jeśli tworzysz obiekt poza implementacją MFC COleObjectFactory.Przyczyny tworzenia agregatów w CCmdTarget::OnCreateAggregates jak również używanie CCmdTarget::GetControllingUnknown staną się jasne po omówieniu tworzenia obiektów kumulowanych.
Ta technika nada Twojemu obiektowi, wszystkie interfejsy, które obiekt agregacji obsługuje plus jego interfejsy macierzyste.Jeśli chcesz tylko podzbioru interfejsów, które obsługuje agregacja, można zastąpić obiekt CCmdTarget::GetInterfaceHook.Pozwala to na bardzo niski poziom możliwości wywoływania, podobny do QueryInterface.Zazwyczaj chcesz mieć wszystkie interfejsy, które obsługuje agregat.
Umożliwianie agregowania implementacji obiektu
Aby obiekt można było agregować, implementacja funkcji AddRef, Release i QueryInterface musi delegować do parametru „kontrolowanie nieznane”. Innymi słowy aby była ona częścią obiektu, to musi delegować funkcje AddRef, Release i QueryInterface do innego obiektu, również pochodzącego z interfejsu IUnknown.To "kontrolowanie nieznane" jest zapewniane dla obiektu podczas jego tworzenia, co oznacza, że jest to zapewniane dla implementacji COleObjectFactory.Zaimplementowanie tego prowadzi do niewielkiej ilości obciążenia, a w niektórych przypadkach nie jest pożądane, więc biblioteka MFC sprawia, że jest to opcjonalne.Aby włączyć agregowanie obiektu, wywołaj CCmdTarget::EnableAggregation z konstruktora obiektu.
Jeśli obiekt korzysta również z agregatów, również należy przekazywać poprawny parametr „kontrolowanie nieznane” do obiektów agregacji.Zazwyczaj ten wskaźnik IUnknown jest przekazywany do obiektu, gdy tworzony jest agregat.Na przykład parametrem pUnkOuter jest „kontrolowanie nieznane” dla obiektów utworzonych za pomocą funkcji CoCreateInstance.Poprawny wskaźnik "kontrolowanie nieznane" może być pobierany przez wywołanie CCmdTarget::GetControllingUnknown.Wartość zwracana z tej funkcji, nie jest jednak prawidłowa podczas konstruktora.Z tego powodu zaleca się tworzyć swoje agregaty tylko w zastąpieniu obiektu CCmdTarget::OnCreateAggregates, gdzie wartość zwracana z funkcji GetControllingUnknown jest wiarygodna, nawet jeśli jest tworzona z implementacji klasy COleObjectFactory.
Jest również ważne, aby obiekt manipulował licznikiem odwołań poprawnych przy dodawaniu lub zwalnianiu zliczania odwołań sztucznych.Aby się upewnić, że tak właśnie jest, zawsze wywołuj ExternalAddRef i ExternalRelease zamiast InternalRelease i InternalAddRef.To rzadkość, aby wywoływać metodę InternalRelease lub InternalAddRef na klasie, która obsługuje agregację.
Materiały źródłowe
Zaawansowane wykorzystanie OLE, takich jak definiowanie własnych interfejsów lub zastępowanie zasad ramowych implementacji interfejsów OLE wymaga użycia podstawowego mechanizmu mapowania interfejsu.
Ten rozdział omawia każde makro i interfejs API, które są używane do wdrażania 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.Przygotowuje to specjalną implementację IUnknown, która jest wymagana dla obiektów kumulowanych.
CCmdTarget::ExternalQueryInterface — Opis funkcji
DWORD ExternalQueryInterface(
const void FAR* lpIID,
LPVOID FAR* ppvObj
);
Uwagi
Parametry
lpIID
Daleki wskaźnik IID (pierwszy argument funkcji QueryInterface)ppvObj
Wskaźnik IUnknown * (drugi argument funkcji QueryInterface)
Uwagi
Wywołaj tę funkcję w danej implementacji IUnknown dla każdego interfejsu, który implementuje klasa.Funkcja ta zapewnia standardową, opartą na danych implementację QueryInterface bazującą na mapie interfejsu użytkownika obiektu.Jest niezbędne rzutowanie zwracanej wartości na błąd HRESULT.Jeśli obiekt jest zagregowany, funkcja ta będzie wywoływać parametr „kontrolowanie IUnknown” zamiast używać lokalnej mapy interfejsów.
CCmdTarget::ExternalAddRef — Opis funkcji
DWORD ExternalAddRef();
Uwagi
Wywołaj tę funkcję w danej implementacji IUnknown::AddRef dla każdego interfejsu, który implementuje klasa.Zwracana wartość jest nowym licznikiem odwołań dla obiektu CCmdTarget.Jeśli obiekt jest zagregowany, funkcja ta będzie wywoływać parametr „kontrolowanie IUnknown” zamiast operować na liczniku odwołań lokalnych.
CCmdTarget::ExternalRelease — Opis funkcji
DWORD ExternalRelease();
Uwagi
Wywołaj tę funkcję w danej implementacji IUnknown::Release dla każdego interfejsu, który implementuje klasa.Zwracana wartość wskazuje nowy licznik odwołań dla obiektu.Jeśli obiekt jest zagregowany, funkcja ta będzie wywoływać parametr „kontrolowanie IUnknown” zamiast operować na liczniku odwołań lokalnych.
DECLARE_INTERFACE_MAP — Opis makra
DECLARE_INTERFACE_MAP
Uwagi
Użyj tego makra w dowolnej klasie pochodnej CCmdTarget z mapą interfejsu.Używane w taki sam sposób jak DECLARE_MESSAGE_MAP.To wywołanie makro powinno być umieszczone w definicji klasy, zazwyczaj w pliku nagłówka (.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 makra
BEGIN_INTERFACE_PART(
localClass,
iface
);
END_INTERFACE_PART(
localClass
)
Uwagi
Parametry
localClass
Nazwa klasy, która implementuje interfejs.iface
Nazwa interfejsu, jaki ta klasa implementuje.
Uwagi
Dla każdego interfejsu, który będzie implementowany przez klasę, musisz mieć parę makr BEGIN_INTERFACE_PART i END_INTERFACE_PART.Te makra definiują lokalną klasę pochodzącą od interfejsu OLE, którą definiujesz, jak również osadzoną zmienną elementu członkowskiego tej klasy.Elementy członkowskie AddRef, Wydanie, i QueryInterface są zgłaszane automatycznie.Musisz dołączyć deklaracje dla innych funkcji członkowskich, będących częścią implementowanego interfejsu (deklaracje te są umieszczane między makrami BEGIN_INTERFACE_PART i END_INTERFACE_PART).
Argument iface jest interfejsem OLE, który chcesz wdrożyć, takim jak IAdviseSink, lub IPersistStorage (lub innym niestandardowym interfejsem użytkownika).
Argument localClass jest nazwą klasy lokalnej, która zostanie zdefiniowana.„X” będzie automatycznie dołączany na początku nazwy.Niniejsza Konwencja nazewnictwa jest używany w celu uniknięcia kolizji z klasami globalnymi o tej samej nazwie.Ponadto nazwa osadzonego elementu członkowskiego jest taka sama jak nazwa obiektu localClass, z wyjątkiem że jest poprzedzona przez wyrażenie „m_x”.
Na 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)
zdefiniuje klasę lokalną o nazwie XMyAdviseSink pochodzącą od IAdviseSink, oraz członka klasy, w której jest zadeklarowana o nazwie m_xMyAdviseSink.Note:
[!UWAGA]
Linie rozpoczynające się od STDMETHOD_ są zasadniczo kopiowane z OLE2.H i nieco modyfikowane.Kopiując je ze OLE2.H, można zmniejszyć liczbę błędów, które są trudne do rozwiązania.
BEGIN_INTERFACE_MAP i END_INTERFACE_MAP — opisy makra
BEGIN_INTERFACE_MAP(
theClass,
baseClass
)
END_INTERFACE_MAP
Uwagi
Parametry
theClass
Klasa, w której mapa interfejsu ma zostać określonabaseClass
Klasa, z której wywodzi się theClass.
Uwagi
Makra BEGIN_INTERFACE_MAP i END_INTERFACE_MAP są używane w pliku implementacji do definiowania mapy interfejsu.Dla każdego interfejsu, który jest implementowany, jest jedno lub więcej wywołań makra INTERFACE_PART.Dla każdej agregacji, z której korzysta ta klasa, jest jedno wywołanie makra INTERFACE_AGGREGATE.
INTERFACE_PART — Opis makra
INTERFACE_PART(
theClass,
iid,
localClass
)
Uwagi
Parametry
theClass
Nazwa klasy, która zawiera mapę interfejsu.iid
IID do mapowania w klasie osadzonej.localClass
Nazwa klasy lokalnej (mniej "X").
Uwagi
To makro jest używane między makro BEGIN_INTERFACE_MAP a makro END_INTERFACE_MAP dla każdego interfejsu, jaki obsługiwał będzie obiekt.Umożliwia mapowania identyfikatora IID do elementu członkowskiego klasy wskazanego przez obiekty theClass i localClass.'m_x' zostanie dodany do localClass automatycznie.Należy zauważyć, że więcej niż jeden obiekt IID może być związany z pojedynczym elementem członkowskim.Jest to bardzo przydatne, gdy przeprowadzasz implementację tylko "najbardziej pochodnego" interfejsu a jednocześnie chcesz zapewnić wszystkie interfejsy pośrednie.Dobrym przykładem jest interfejs IOleInPlaceFrameWindow.Jego hierarchia wygląda następująco:
IUnknown
IOleWindow
IOleUIWindow
IOleInPlaceFrameWindow
Jeśli obiekt implementuje interfejs IOleInPlaceFrameWindow, klient może wykonać metodę QueryInterface na każdym z tych interfejsów: IOleUIWindow, IOleWindow lub IUnknown, poza „najbardziej pochodnym” interfejsem IOleInPlaceFrameWindow (tym, który jest faktycznie implementowany).Aby się tym zająć można użyć więcej niż jednego makro INTERFACE_PART, aby mapować każdy interfejs podstawowy do interfejsu IOleInPlaceFrameWindow:
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
Szablon dba o IUnknown, ponieważ ten element jest zawsze wymagany.
INTERFACE_PART — Opis makra
INTERFACE_AGGREGATE(
theClass,
theAggr
)
Uwagi
Parametry
theClass
Nazwa klasy, która zawiera mapę interfejsu,theAggr
Nazwa zmiennej członkowskiej, która ma zostać zagregowana.
Uwagi
To makro jest używane, aby poinformować szablon, że klasa korzysta z obiektu agregacji.Musi się znajdować między makrami BEGIN_INTERFACE_PART i END_INTERFACE_PART.Obiekt agregacji jest oddzielnym obiektem, pochodzącym z IUnknown.Za pomocą agregacji i makra INTERFACE_AGGREGATE makra można sprawić, aby wszystkie interfejsy, które obsługuje agregacja, wyglądały na bezpośrednio obsługiwane przez obiekt.Argument theAggr jest po prostu nazwą zmiennej członka klasy wywodzącej się od IUnknown (bezpośrednio lub pośrednio).Wszystkie makra INTERFACE_AGGREGATE muszą być po INTERFACE_PART, gdy zostaną umieszczone na mapie interfejsu.