Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Notatka
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 jest 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.
OLE odnosi się do wszystkich klas pochodzących z IUnknown jako "interfejsy". Jest to ważne rozróżnienie, ponieważ "interfejs", taki jak IUnknown, nie zawiera ż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ę IUnknown MFC, musisz najpierw zrozumieć, czym jest ten interfejs. Uproszczona wersja IUnknown jest zdefiniowana poniżej:
class IUnknown
{
public:
virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
Notatka
Niektóre niezbędne szczegóły konwencji wywoływania, takie jak __stdcall, zostały pominięte na tej ilustracji.
Funkcje członkowskie AddRef i Release kontrolują zarządzanie pamięcią obiektu. COM używa schematu zliczania odwołań, aby śledzić obiekty. Obiekt nigdy nie jest referencją bezpośrednio, tak jak w C++. Zamiast tego obiekty COM zawsze odwołują się do wskaźnika. Aby zwolnić obiekt po zakończeniu jego używania przez właściciela, wywoływana jest funkcja członkowska obiektu Release (w przeciwieństwie do użycia operatora delete, jak to się robi w przypadku tradycyjnego obiektu C++). Mechanizm zliczania odwołań umożliwia zarządzanie wieloma odwołaniami do pojedynczego obiektu. Implementacja AddRef i Release utrzymuje liczbę odwołań względem obiektu — obiekt nie zostanie usunięty, dopóki liczba odwołań nie osiągnie zera.
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 QueryInterface jest nieco bardziej interesująca. Nie jest zbyt interesujące mieć obiekt, który ma tylko funkcje członkowskie AddRef i Release — warto byłoby poinformować obiekt, aby zrobił więcej rzeczy niż zapewnia IUnknown. Jest to miejsce, w którym QueryInterface jest przydatna. Umożliwia uzyskanie innego "interfejsu" na tym samym obiekcie. Te interfejsy są zwykle pochodnymi IUnknown i dodają dodatkową funkcjonalność, 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. Na przykład
class IPrintInterface : public IUnknown
{
public:
virtual void PrintObject() = 0;
};
Aby uzyskać interfejs IPrintInterface, jeśli masz tylko IUnknown, wywołaj QueryInterface przy użyciu IID dla IPrintInterface.
IID jest 128-bitową liczbą, która jednoznacznie identyfikuje interfejs. Istnieje IID dla każdego interfejsu, który Ty lub OLE definiujesz. Jeśli pUnk jest wskaźnikiem do obiektu IUnknown, kod do pobrania z niego interfejsu IPrintInterface może wyglądać następująco:
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 zaimplementować obiekt obsługujący zarówno interfejs IPrintInterface, jak i IUnknown? W tym przypadku jest to proste, ponieważ interfejs IPrintInterface jest bezpośrednio pochodną IUnknown — implementując interfejs IPrintInterface, IUnknown jest automatycznie obsługiwany. 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 Release byłyby dokładnie takie same jak te zaimplementowane powyżej.
CPrintObj::QueryInterface wygląda 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 wykonanie QueryInterface implikuje dorozumiane użycie AddRef. Oczywiście należy również zaimplementować CEditObj::Print. 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 IUnknown, rozważ następujące kwestie:
class IEditInterface : public IUnkown
{
public:
virtual void EditObject() = 0;
};
Chociaż istnieje wiele różnych sposobów implementacji klasy obsługującej IEditInterface i IPrintInterface, w tym poprzez użycie wielodziedziczenia w 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);
}
Zauważ, ż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ć 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 mogły wywołać funkcję implementacyjną 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::PrintObject i CEditPrintObj::CEditObj::EditObject. Dodano sporo kodu, aby dodać jedną funkcję — możliwość edytowania obiektu. Na szczęście, dość rzadko zdarza się, że interfejsy mają tylko jedną funkcję składową (chociaż czasami tak się dzieje), a w takim przypadku interfejsy 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ą map komunikatów. Ta funkcja jest nazywana mapami interfejsów i została omówiona w następnej sekcji.
Mapy interfejsu MFC
MFC/OLE zawiera implementację "Map interfejsu" podobną do "Map komunikatów" MFC i "Dispatch Maps" w koncepcji i wykonaniu. Podstawowe funkcje map interfejsów MFC są następujące:
Standardowa implementacja IUnknownwbudowana w klasę
CCmdTarget.Utrzymanie licznika referencji, zmodyfikowane przez AddRef i Release
Implementacja sterowana danymi QueryInterface
Ponadto mapy interfejsu obsługują następujące funkcje zaawansowane:
Obsługa tworzenia agregowalnych 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 klasie CCmdTarget.
CCmdTarget "ma" liczbę odwołań, a także wszystkie funkcje składowe skojarzone z implementacją IUnknown (na przykład liczba odwołań znajduje się w CCmdTarget). Aby utworzyć klasę, która obsługuje interfejs OLE COM, należy utworzyć klasę z CCmdTarget i używać różnych makr, a także funkcji składowych CCmdTarget 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 interfejsu IUnknown oraz wielu makr, które usuwają powtarzający się kod.
Podstawy mapy interfejsu
Aby zaimplementować klasę przy użyciu map interfejsu MFC
Utwórz klasę bezpośrednio lub pośrednio z
CCmdTarget.Użyj funkcji
DECLARE_INTERFACE_MAPw definicji klasy pochodnej.Dla każdego interfejsu, który chcesz obsługiwać, 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, aby przypisać ten identyfikator do określonej "części" klasy.
Zaimplementuj każdą z zagnieżdżonych klas, które reprezentują obsługiwane interfejsy.
Użyj makra METHOD_PROLOGUE, aby uzyskać dostęp do obiektu nadrzędnego, pochodnego od
CCmdTarget.AddRef, Releasei QueryInterface mogą delegować do implementacji tych funkcji
CCmdTarget(ExternalAddRef,ExternalReleaseiExternalQueryInterface).
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 CCmdTarget. 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"). Tworzeni są dwaj zagnieżdżeni członkowie tych klas: m_CEditObj i m_CPrintObj. Makra automatycznie deklarują funkcje AddRef, Releasei QueryInterface; dlatego deklarujesz tylko funkcje specyficzne dla tego interfejsu: EditObject i PrintObject (makro OLE STDMETHOD jest używane, aby słowo kluczowe _stdcall oraz „virtual” 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, gdy jest to wymagane. Nie jest konieczne uwzględnienie wpisu dla IID_IUnknown; platforma będzie używać pierwszego interfejsu na mapie (w tym przypadku m_CPrintObj) podczas żądania IID_IUnknown.
Mimo że makro BEGIN_INTERFACE_PART automatycznie zadeklarowało AddRef, Release i QueryInterface funkcje, 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:
Zaimplementuj QueryInterface
Implementacja metod AddRef i Release
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, na przykład 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.
Notatka
Zazwyczaj nie jest możliwe ponowne wykorzystanie wbudowanych implementacji interfejsów OLE w MFC tylko przez dziedziczenie specjalizacji wbudowanej tego interfejsu z wersji MFC. Nie jest to możliwe, ponieważ użycie makra METHOD_PROLOGUE w celu uzyskania dostępu do zawierającego CCmdTarget-pochodnego obiektu oznacza stałe przesunięcie obiektu osadzonego z obiektu pochodnego CCmdTarget. Oznacza to na przykład, że nie można utworzyć osadzonego elementu XMyAdviseSink z implementacji MFC w COleClientItem::XAdviseSink, ponieważ XAdviseSink opiera się na określonym przesunięciu od góry obiektu COleClientItem.
Notatka
Możesz jednak delegować do implementacji MFC dla wszystkich funkcji, dla których chcesz domyślne zachowanie MFC. Odbywa się to w implementacji MFC IOleInPlaceFrame (XOleInPlaceFrame) w klasie COleFrameHook. Dla wielu funkcji deleguje ona zadania do m_xOleInPlaceUIWindow. 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żonym tematem, aby go tutaj omówić; zapoznaj się z tematem Agregacja, aby uzyskać więcej informacji na temat agregacji. 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. Więc jak to wiąże się z mechanizmem mapowania interfejsów MFC? Oprócz makra INTERFACE_PART, gdzie zagnieżdżony obiekt jest mapowany na identyfikator IID, można również zadeklarować obiekt agregacji jako część klasy pochodnej od 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 IUnknown lub klasy pochodnej), która ma być zintegrowana z mechanizmem mapy interfejsu. Jeśli wskaźnik nie ma wartości NULL w momencie wywołania CCmdTarget::ExternalQueryInterface, framework automatycznie wywoła funkcję składową QueryInterface obiektu agregowanego, jeśli żądane IID nie jest jedną z natywnych IIDobsługiwanych przez sam obiekt CCmdTarget.
Aby użyć makra INTERFACE_AGGREGATE
Zadeklaruj zmienną składową (
IUnknown*), która będzie zawierać wskaźnik do obiektu agregowanego.Uwzględnij makro INTERFACE_AGGREGATE na mapie interfejsu, które odwołuje się do zmiennej składowej według nazwy.
W pewnym momencie (zwykle podczas
CCmdTarget::OnCreateAggregates) zainicjuj zmienną składową na inną wartość 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 na wartość NULL. Struktura ignoruje zmienną składową NULL w domyślnej implementacji QueryInterface.
OnCreateAggregates jest właściwym miejscem do tworzenia obiektów agregacji. Należy ją jawnie wywołać, jeśli tworzysz obiekt poza implementacją MFC COleObjectFactory. Przyczyna tworzenia agregacji w CCmdTarget::OnCreateAggregates, a także użycia CCmdTarget::GetControllingUnknown, stanie się zrozumiała, gdy zostanie omówione tworzenie obiektów agregowalnych.
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. Pozwala to na bardzo niskie możliwości zaczepienia, podobnie jak QueryInterface. Zwykle potrzebujesz wszystkich interfejsów, które obsługuje agregacja.
Tworzenie implementacji obiektu, którą można agregować
Aby obiekt był agregowalny, implementacja AddRef, Releasei QueryInterface musi być delegowana do „kontrolującego nieznanego”. Innymi słowy, aby był częścią obiektu, musi delegować AddRef, Releasei QueryInterface do innego obiektu, również pochodzącego z IUnknown. Ten "nieznany kontroler" jest dostarczany do obiektu podczas jego tworzenia, czyli 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ć obiektowi bycie agregowalnym, należy wywołać CCmdTarget::EnableAggregation z konstruktora obiektu.
Jeśli obiekt używa również agregacji, należy również przekazać poprawne "kontrolowanie nieznane" do obiektów agregujących. Zwykle ten wskaźnik IUnknown jest przekazywany do obiektu podczas tworzenia agregacji. Na przykład parametr pUnkOuter to "kontrolowanie nieznanych" obiektów utworzonych za pomocą CoCreateInstance. Prawidłowy wskaźnik "kontrolowanie nieznany" można pobrać, wywołując CCmdTarget::GetControllingUnknown. Wartość zwrócona z tej funkcji jest jednak nieprawidłowa w trakcie działania konstruktora. Z tego powodu zaleca się utworzenie agregacji tylko w zastąpieniu CCmdTarget::OnCreateAggregates, gdzie wartość zwracana z GetControllingUnknown jest niezawodna, nawet jeśli została utworzona na podstawie implementacji COleObjectFactory.
Ważne jest również, aby obiekt manipulował prawidłowym licznikiem odwołań podczas dodawania lub zwalniania sztucznych liczników odwołań. Aby upewnić się, że tak jest, zawsze należy wywołać ExternalAddRef i ExternalRelease zamiast InternalRelease i InternalAddRef. Rzadko zdarza się wywoływać InternalRelease lub InternalAddRef w klasie, która obsługuje agregację.
Materiał referencyjny
Zaawansowane użycie OLE, takie jak definiowanie własnych interfejsów lub nadpisywanie implementacji interfejsów OLE przez ramę, wymaga użycia podstawowego mechanizmu mapowania interfejsów.
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 IUnknown wymaganej dla obiektów agregowalnych.
CCmdTarget::ExternalQueryInterface — opis funkcji
DWORD ExternalQueryInterface(
const void FAR* lpIID,
LPVOIDFAR* ppvObj
);
Parametry
Identyfikator lpIID
Wskaźnik dalekiego zasięgu do identyfikatora IID (pierwszy argument dla QueryInterface)
ppvObj
Wskaźnik do elementu IUnknown* (drugi argument zapytaniaInterface)
Uwagi
Wywołaj tę funkcję w implementacji IUnknown dla każdego interfejsu, który implementuje klasa. Ta funkcja zapewnia standardową, opartą na danych implementację QueryInterface, bazującą na mapie interfejsu Twojego obiektu. Konieczne jest rzutowanie wartości zwracanej na 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 IUnknown::AddRef dla każdego interfejsu, który implementuje Twoja klasa. 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 IUnknown::Release dla każdego interfejsu, który implementuje twoja klasa. 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, która będzie mieć mapę interfejsu. Używane w taki sam sposób jak DECLARE_MESSAGE_MAP. Wywołanie tego makra należy umieścić w definicji klasy, zwykle w pliku nagłówkowym (.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
interfejs
Nazwa interfejsu, który implementuje ta klasa
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. Składowe AddRef, Releasei QueryInterface są deklarowane automatycznie. Należy dołączyć deklaracje dla innych funkcji składowych, które są częścią implementowanego interfejsu (deklaracje te są umieszczane między makrami BEGIN_INTERFACE_PART i END_INTERFACE_PART).
Argumentem dla jest interfejs OLE, który chcesz zaimplementować, taki jak IAdviseSinklub IPersistStorage (albo własny niestandardowy interfejs).
Argument localClass jest nazwą 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 jest taka sama jak nazwa localClass, z tym wyjątkiem, że poprzedza ją prefiks „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)
Zdefiniuj klasę lokalną o nazwie XMyAdviseSink, pochodzącą z IAdviseSink, oraz element członkowski klasy, w której jest zadeklarowany, o nazwie m_xMyAdviseSink. Uwaga:
Notatka
Wiersze rozpoczynające się od STDMETHOD_ są zasadniczo kopiowane z pliku OLE2.H i nieznacznie zmodyfikowane. 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
Klasa
Klasa, w której ma zostać zdefiniowana mapa interfejsu
baseClass
Klasa, z której wywodzi się , to.
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
Klasa
Nazwa klasy zawierającej mapę interfejsu.
identyfikator
IID, który ma zostać zamapowany na klasę osadzoną.
localClass
Nazwa klasy lokalnej (z pominięciem "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 zmapowanie identyfikatora IID na element klasy wskazanej przez theClass i localClass. Element "m_x" zostanie dodany do localClass automatycznie. Należy pamiętać, że z jednym członkiem może być skojarzonych więcej niż jeden IID. Jest to bardzo przydatne, gdy implementujesz tylko interfejs "najbardziej pochodny" i chcesz również udostępnić 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 IOleInPlaceFrameWindow, klient może QueryInterface na dowolny z tych interfejsów: IOleUIWindow, IOleWindowlub IUnknown, oprócz interfejsu "najbardziej pochodnego" IOleInPlaceFrameWindow (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 na interfejs 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
Framework zajmuje się interfejsem IUnknown, ponieważ jest on zawsze wymagany.
INTERFACE_PART — opis makra
INTERFACE_AGGREGATE(theClass, theAggr)
Parametry
Klasa
Nazwa klasy, która zawiera mapę interfejsu,
aggr
Nazwa zmiennej składowej, która ma być agregowana.
Uwagi
To makro służy do informowania frameworku, ż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 IUnknown. Używając agregatu i makra INTERFACE_AGGREGATE, można sprawić, że wszystkie interfejsy, które obsługuje agregat, będą wydawać się bezpośrednio obsługiwane przez obiekt. Argument to po prostu nazwa zmiennej składowej twojej klasy, która wywodzi się z 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