TN038: Implementieren von MFC/OLE-IUnknown

Hinweis

Der folgende technische Hinweis wurde seit dem ersten Erscheinen in der Onlinedokumentation nicht aktualisiert. Daher können einige Verfahren und Themen veraltet oder falsch sein. Um aktuelle Informationen zu erhalten, wird empfohlen, das gewünschte Thema im Index der Onlinedokumentation zu suchen.

Das Herzstück von OLE 2 ist das "OLE-Component Object Model" oder COM. COM definiert einen Standard für die Kommunikation kooperierender Objekte miteinander. Dazu gehören Details zum Aussehen eines "Objekts", einschließlich wie Methoden in einem Objekt weitergeleitet werden. COM definiert auch eine Basisklasse, von der alle COM-kompatiblen Klassen abgeleitet werden. Diese Basisklasse ist IUnknown. Obwohl die IUnknown-Schnittstelle als C++-Klasse bezeichnet wird, ist COM nicht spezifisch für eine Sprache – sie kann in C, PASCAL oder einer anderen Sprache implementiert werden, die das binäre Layout eines COM-Objekts unterstützen kann.

OLE bezieht sich auf alle von IUnknown abgeleiteten Klassen als "Schnittstellen". Dies ist eine wichtige Unterscheidung, da eine "Schnittstelle" wie IUnknown mit ihr keine Implementierung trägt. Sie definiert einfach das Protokoll, über das Objekte kommunizieren, nicht die speziellen Aufgaben dieser Implementierungen. Dies ist für ein System, das maximale Flexibilität zulässt, sinnvoll. Es ist die Aufgabe von MFC, ein Standardverhalten für MFC-/C++-Programme zu implementieren.

Um die Implementierung von IUnknown von MFC zu verstehen, müssen Sie zunächst verstehen, was diese Schnittstelle ist. Unten wird eine vereinfachte Version von IUnknown definiert:

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

Hinweis

Bestimmte erforderliche Aufrufkonventionsdetails, wie __stdcall, werden für diese Abbildung außer acht gelassen.

Die AddRef - und Release-Memberfunktionen steuern die Speicherverwaltung des Objekts. COM verwendet Verweiszählungsschema, um Objekte nachzuverfolgen. Auf ein Objekt wird nie direkt verwiesen wie in C++. Stattdessen wird auf COM-Objekte immer durch einen Zeiger verwiesen. Um das Objekt freizugeben, wenn der Besitzer es verwendet, wird das Release-Element des Objekts aufgerufen (im Gegensatz zur Verwendung des Operatorlöschvorgangs, wie für ein herkömmliches C++-Objekt). Dank des Verweiszählmechanismus können mehrere Verweise auf ein einzelnes Objekt verwaltet werden. Eine Implementierung von AddRef und Release Standard enthält eine Verweisanzahl für das Objekt – das Objekt wird erst gelöscht, wenn die Referenzanzahl null erreicht.

AddRef und Release sind relativ einfach aus Implementierungssicht. Im Folgenden wird eine einfache Implementierung veranschaulicht:

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

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

Die QueryInterface-Memberfunktion ist etwas interessanter. Es ist nicht sehr interessant, ein Objekt zu haben, dessen einzige Memberfunktionen AddRef und Release sind – es wäre schön, das Objekt anzuweisen, mehr Aufgaben zu erledigen, als IUnknown bereitstellt. Hier ist QueryInterface nützlich. Sie ermöglicht Ihnen, eine andere "Schnittstelle" für dasselbe Objekt zu erhalten. Diese Schnittstellen werden in der Regel von IUnknown abgeleitet und fügen zusätzliche Funktionen hinzu, indem neue Memberfunktionen hinzugefügt werden. Für COM-Schnittstellen werden nie Membervariablen in der Schnittstelle deklariert, und alle Memberfunktionen werden als rein virtuell deklariert. Ein auf ein Objekt angewendeter

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

Um ein IPrintInterface zu erhalten, wenn Sie nur über einen IUnknown verfügen, rufen Sie QueryInterface mithilfe IID der der IPrintInterface Eine IID ist eine 128-Bit-Zahl, die die Schnittstelle eindeutig identifiziert. Für jede Schnittstelle gibt es eine IID, die entweder von Ihnen oder OLE definiert wird. Wenn pUnk ein Zeiger auf ein IUnknown-Objekt ist, kann der Code zum Abrufen eines IPrintInterface-Elements wie folgt lauten:

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

Das scheint ziemlich einfach, aber wie würden Sie ein Objekt implementieren, das sowohl die IPrintInterface- als auch die IUnknown-Schnittstelle unterstützt. In diesem Fall ist es einfach, da die IPrintInterface direkt von IUnknown abgeleitet wird – durch die Implementierung von IPrintInterface wird IUnknown automatisch unterstützt. Beispiel:

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

Die Implementierungen von AddRef und Release entsprechen genau den oben implementierten Implementierungen. CPrintObj::QueryInterface würde etwa wie folgt aussehen:

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

Wenn der Schnittstellenbezeichner (IID) erkannt wird, wird ein Zeiger auf das Objekt zurückgegeben. Andernfalls tritt ein Fehler auf. Beachten Sie außerdem, dass ein erfolgreiches QueryInterface zu einem implizierten AddRef führt. Natürlich müssten Sie auch CEditObj::Print implementieren. Das ist einfach, da die IPrintInterface direkt von der IUnknown-Schnittstelle abgeleitet wurde. Wenn Sie jedoch zwei verschiedene Schnittstellen unterstützen möchten, die beide von IUnknown abgeleitet sind, sollten Sie Folgendes berücksichtigen:

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

Obwohl es mehrere Möglichkeiten gibt, eine Klasse zu implementieren, die IEditInterface und IPrintInterface unterstützt, einschließlich Verwenden der C++-Mehrfachvererbung, konzentriert sich dieser Hinweis auf die Verwendung von geschachtelten Klassen zur Implementierung dieser Funktionalität.

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;
};

Die gesamte Implementierung wird unten aufgeführt:

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);
}

Beachten Sie, dass die meisten IUnknown-Implementierungen in die CEditPrintObj-Klasse eingefügt werden, anstatt den Code in CEditPrintObj::CEditObj und CEditPrintObj::CPrintObj zu duplizieren. So wird die Codemenge reduziert und Fehler vermieden. Der wichtigste Punkt hier ist, dass es über die IUnknown-Schnittstelle möglich ist, QueryInterface aufzurufen, um jede Schnittstelle abzurufen, die das Objekt unterstützen kann, und von jeder dieser Schnittstellen ist es möglich, dasselbe zu tun. Dies bedeutet, dass sich alle von jeder Schnittstelle verfügbaren QueryInterface-Funktionen genau auf die gleiche Weise verhalten müssen. Damit diese eingebetteten Objekte die Implementierung im "äußeren Objekt" aufrufen kann, wird ein Gegenzeiger verwendet (m_pParent). Der m_pParent-Zeiger wird während des CEditPrintObj-Konstruktors initialisiert. Anschließend würden Sie CEditPrintObj::CPrintObj::PrintObject und CEditPrintObj::CEditObj::EditObject ebenfalls implementieren. Es wurde relativ viel Code für eine Funktion, die Fähigkeit zum Bearbeiten des Objekts, hinzugefügt. Glücklicherweise ist es recht selten, dass Schnittstellen nur über eine einzelne Memberfunktion verfügen (es kann jedoch durchaus vorkommen) und in diesem Fall würden EditObject und PrintObject normalerweise zu einer einzelnen Schnittstelle kombiniert werden.

Das ist eine umfassende Erläuterung und viel Code für ein solch einfaches Szenario. Die MFC/OLE-Klassen stellen eine einfachere Alternative zur Verfügung. Die MFC-Implementierung verwendet eine Technik, die mit der Methode vergleichbar ist, mit der Windows-Meldungen mit Meldungszuordnungen umschlossen werden. Diese Einrichtung wird als Interface Karten bezeichnet und wird im nächsten Abschnitt erläutert.

MFC-Schnittstellenzuordnungen

MFC/OLE beinhaltet die Implementierung von "Schnittstellenzuordnungen", die in Bezug auf Konzept und in der Ausführung den "Meldungszuordnungen" und "Dispatchzuordnungen" von MFC ähneln. Die Kernfunktionen der Schnittstellenzuordnungen von MFC sind folgende:

  • Eine Standardimplementierung von IUnknown, die in die CCmdTarget Klasse integriert ist.

  • Wartung der Referenzanzahl, geändert von AddRef und Release

  • Datengesteuerte Implementierung von QueryInterface

Außerdem unterstützen Schnittstellenzuordnungen die folgenden erweiterten Funktionen:

  • Unterstützung für das Erstellen von aggregatfähigen COM-Objekten

  • Unterstützung für das Verwenden von aggregierten Objekten in der Implementierung eines COM-Objekts

  • Die Implementierung ist "hookable" und erweiterbar

Weitere Informationen zur Aggregation finden Sie im Thema "Aggregation" .

Die Unterstützung der Schnittstellenzuordnung von MFC haben ihren Stammpfad in der CCmdTarget-Klasse. CCmdTarget "has-a"-Verweisanzahl sowie alle Memberfunktionen, die der IUnknown-Implementierung zugeordnet sind (die Referenzanzahl ist z. B. in CCmdTarget). Um eine Klasse zu erstellen, die OLE-COM unterstützt, leiten Sie eine Klasse von CCmdTarget ab, und verwenden Sie verschiedene Makros sowie Memberfunktionen von CCmdTarget, um die gewünschten Schnittstellen zu implementieren. Die Implementierung von MFC verwendet geschachtelte Klassen, um die jeweilige Schnittstellenimplementierung ähnlich wie im Beispiel oben zu definieren. Dies wird mit einer Standardimplementierung von IUnknown sowie mit einer Reihe von Makros erleichtert, durch die ein Teil des sich wiederholenden Codes entfällt.

Grundlagen zu Schnittstellenzuordnungen

So implementieren Sie eine Klasse mithilfe der Schnittstellenzuordnungen von MFC

  1. Leiten Sie eine Klasse entweder direkt oder indirekt von CCmdTarget ab.

  2. Verwenden Sie die DECLARE_INTERFACE_MAP-Function in der abgeleiteten Klassendefinition.

  3. Verwenden Sie für jede Schnittstelle, die Sie unterstützen möchten, die BEGIN_INTERFACE_PART und END_INTERFACE_PART Makros in der Klassendefinition.

  4. Verwenden Sie in der Implementierungsdatei die BEGIN_INTERFACE_MAP und END_INTERFACE_MAP Makros, um die Schnittstellenzuordnung der Klasse zu definieren.

  5. Verwenden Sie für jede unterstützte IID das INTERFACE_PART Makro zwischen dem BEGIN_INTERFACE_MAP und END_INTERFACE_MAP Makros, um diese IID einem bestimmten "Teil" Ihrer Klasse zuzuordnen.

  6. Implementieren Sie die geschachtelten Klassen, die die von Ihnen unterstützten Schnittstellen darstellen.

  7. Verwenden Sie das METHOD_PROLOGUE Makro, um auf das übergeordnete, CCmdTargetabgeleitete Objekt zuzugreifen.

  8. AddRef, Release und QueryInterface können an die CCmdTarget Implementierung dieser Funktionen (ExternalAddRef, ExternalReleaseund ExternalQueryInterface) delegieren.

Das obige CPrintEditObj-Beispiel kann implementiert wie folgt werden:

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)
};

Die obige Deklaration erstellt eine Klasse, die von CCmdTarget abgeleitet wird. Das DECLARE_INTERFACE_MAP Makro teilt dem Framework mit, dass diese Klasse über eine benutzerdefinierte Schnittstellenzuordnung verfügt. Darüber hinaus definieren die BEGIN_INTERFACE_PART- und END_INTERFACE_PART Makros geschachtelte Klassen, in diesem Fall mit den Namen CEditObj und CPrintObj (das X wird nur verwendet, um die geschachtelten Klassen von globalen Klassen zu unterscheiden, die mit "C" und Schnittstellenklassen beginnen, die mit "I" beginnen). Zwei geschachtelte Member dieser Klassen werden erstellt: m_CEditObj bzw. m_CPrintObj. Die Makros deklarieren automatisch die Funktionen "AddRef", "Release" und "QueryInterface". Daher deklarieren Sie nur die für diese Schnittstelle spezifischen Funktionen: EditObject und PrintObject (das OLE-Makro STDMETHOD wird verwendet, sodass _stdcall und virtuelle Schlüsselwort (keyword) für die Zielplattform bereitgestellt werden).

So implementieren Sie Schnittstellenzuordnung für diese Klasse:

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

Damit wird die IID "IID_IPrintInterface" mit "m_CPrintObj" und "IID_IEditInterface" mit "m_CEditObj" verbunden. Die CCmdTarget Implementierung von QueryInterface (CCmdTarget::ExternalQueryInterface) verwendet diese Zuordnung, um Zeiger auf m_CPrintObj und m_CEditObj zurückzugeben, wenn sie angefordert werden. Es ist nicht erforderlich, einen Eintrag für IID_IUnknown einzuschließen. Das Framework verwendet die erste Schnittstelle in der Zuordnung (in diesem Fall "m_CPrintObj"), wenn IID_IUnknown angefordert wird.

Obwohl das BEGIN_INTERFACE_PART Makro automatisch die Funktionen "AddRef", "Release " und "QueryInterface " für Sie deklariert hat, müssen Sie sie trotzdem implementieren:

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...
}

Die Implementierung für CEditPrintObj::CPrintObj entspricht weitgehend den oben beschriebenen Definitionen für CEditPrintObj::CEditObj. Obwohl es möglich wäre, ein Makro zu erstellen, mit dem diese Funktionen automatisch generiert werden (was am Anfang der MFC/OLE-Entwicklung der Fall war), wird es schwierig, Haltepunkte festzulegen, wenn ein Makro mehrere Codezeilen generiert. Aus diesem Grund wird dieser Code manuell erweitert.

Durch Verwendung der Frameworkimplementierung von Meldungszuordnungen mussten einige Vorgänge nicht ausgeführt werden:

  • Implementieren von QueryInterface

  • Implementieren von AddRef und Release

  • Deklarieren eine dieser integrierten Methoden auf beiden Schnittstellen

Darüber hinaus verwendet das Framework Meldungszuordnungen intern. So können Sie aus einer Frameworkklasse ableiten, beispielsweise COleServerDoc, die bereits bestimmte Schnittstellen unterstützt und entweder Ersatz oder Hinzufügungen zu den Schnittstellen bereitstellt, die im Framework enthalten sind. Dies ist möglich, da das Framework das Erben einer Schnittstellenzuordnung von einer Basisklasse vollständig unterstützt. Aus diesem Grund verwendet BEGIN_INTERFACE_MAP als zweiten Parameter den Namen der Basisklasse.

Hinweis

Es ist im Allgemeinen nicht möglich, die Implementierung der integrierten MFC-Implementierungen der OLE-Schnittstellen nur durch Vererben der eingebetteten Spezialisierung dieser Schnittstelle von der MFC-Version wiederzuverwenden. Dies ist nicht möglich, da die Verwendung des METHOD_PROLOGUE Makros, um Zugriff auf das enthaltende CCmdTargetabgeleitete Objekt zu erhalten, einen festen Offset des eingebetteten Objekts aus dem CCmdTargetabgeleiteten Objekt impliziert. Dies bedeutet z. B., dass Sie kein eingebettetes XMyAdviseSink von der MFC-Implementierung in COleClientItem::XAdviseSink ableiten können, da XAdviseSink sich an einem bestimmten Offset oben im COleClientItem-Objekt befinden muss.

Hinweis

Sie können alle Funktionen, für die das Standardverhalten von MFC gelten soll, jedoch an die MFC-Implementierung delegieren. Dies wird in der MFC-Implementierung von IOleInPlaceFrame (XOleInPlaceFrame) in der COleFrameHook-Klasse ausgeführt (sie delegiert viele Funktionen zu m_xOleInPlaceUIWindow). Dieser Entwurf wurde ausgewählt, um die Laufzeitgröße von Objekten zu reduzieren, die viele Schnittstellen implementieren. Mit diesem Entwurf ist kein Gegenzeiger mehr erforderlich (wie bei Verwendung von m_pParent im vorherigen Abschnitt).

Aggregation und Schnittstellenzuordnungen

Zusätzlich zur Unterstützung von eigenständigen COM-Objekten unterstützt MFC auch Aggregation. Aggregation selbst ist zu komplex, um hier zu diskutieren; weitere Informationen zur Aggregation finden Sie im Thema "Aggregation ". Dieser Hinweis beschreibt einfach die Unterstützung für die Aggregation, die in den Framework und die Schnittstellenzuordnungen integriert ist.

Es gibt zwei Möglichkeiten, Aggregation zu verwenden: (1) mit einem COM-Objekt, das Aggregation unterstützt und (2) durch Implementierung eines Objekts, von einem anderen aggregiert werden kann. Diese Funktionen können als "Verwenden eines Aggregatobjekts" und "Ausstatten einer Objekt-Implementierung mit Aggregatfähigkeit" bezeichnet werden. MFC unterstützt beide Funktionen.

Verwenden eines Aggregatobjekts

Zur Verwendung eines Aggregatobjekts, muss es eine Methode geben, um das Aggregat in den QueryInterface-Mechanismus einzubinden. Das heißt, das Aggregatobjekt muss sich wie ein systemeigener Teil des Objekts verhalten. Wie funktioniert dies also mit dem Schnittstellenzuordnungsmechanismus von MFC. Zusätzlich zum INTERFACE_PART Makro, bei dem ein geschachteltes Objekt einem IID zugeordnet ist, können Sie auch ein Aggregatobjekt als Teil der CCmdTarget abgeleiteten Klasse deklarieren. Dazu wird das INTERFACE_AGGREGATE Makro verwendet. Auf diese Weise können Sie eine Membervariable angeben (die ein Zeiger auf eine IUnknown - oder abgeleitete Klasse sein muss), die in den Schnittstellenzuordnungsmechanismus integriert werden soll. Wenn der Zeiger beim CCmdTarget::ExternalQueryInterface Aufrufen nicht NULL ist, ruft das Framework automatisch die QueryInterface-Memberfunktion des Aggregatobjekts auf, wenn die IID angeforderte nicht einer der systemeigenen IIDElemente ist, die CCmdTarget vom Objekt selbst unterstützt werden.

So verwenden Sie das INTERFACE_AGGREGATE-Makro

  1. Deklarieren Sie eine Membervariable (IUnknown*), die einen Zeiger auf das Aggregatobjekt enthält.

  2. Fügen Sie ein INTERFACE_AGGREGATE Makro in die Schnittstellenzuordnung ein, das sich auf die Membervariable anhand des Namens bezieht.

  3. Daraufhin (normalerweise während CCmdTarget::OnCreateAggregates) initialisieren Sie die Membervariable auf einen anderen Wert als NULL.

Beispiel:

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()

Die m_lpAggrInner-Variable wird im Konstruktor auf NULL initialisiert. Das Framework ignoriert eine NULL-Membervariable in der Standardimplementierung von QueryInterface. Das OnCreateAggregates-Objekt eignet sich gut, um die Aggregatobjekte tatsächlich zu erstellen. Sie müssen dieses Objekt explizit aufrufen, wenn Sie das Objekt außerhalb der MFC-Implementierung von COleObjectFactory erstellen. Der Grund für das Erstellen von Aggregaten in CCmdTarget::OnCreateAggregates und die Verwendung von CCmdTarget::GetControllingUnknown wird offensichtlich, wenn das Erstellen von aggregatfähigen Objekten erläutert wird.

Mit dieser Methode erhält das Objekt alle Schnittstellen, die das Aggregatobjekt unterstützt, sowie deren systemeigene Schnittstellen. Wenn Sie nur eine Teilmenge der Schnittstellen benötigen, die das Aggregat unterstützt, können Sie CCmdTarget::GetInterfaceHook überschreiben. Dies ermöglicht ihnen sehr niedrige Hookability, ähnlich wie QueryInterface. Normalerweise werden alle Schnittstellen gewünscht, die das Aggregat unterstützt.

Ausstatten einer Objekt-Implementierung mit Aggregationsfähigkeit

Damit ein Objekt aggregierbar ist, muss die Implementierung von AddRef, Release und QueryInterface an ein "Controlling unknown" delegieren. Anders ausgedrückt: Damit es Teil des Objekts ist, muss es AddRef, Release und QueryInterface an ein anderes Objekt delegieren, auch von IUnknown abgeleitet. Dieses "controlling unknown"-Objekt wird dem Objekt beim Erstellen zur Verfügung gestellt, d. h., es wird der Implementierung von COleObjectFactory bereitgestellt. Das Implementieren bedeutet einen geringen Mehraufwand, und in einigen Fällen ist es nicht wünschenswert, sodass dies laut MFC optional ist. Um ein Objekt aggregatfähig zu machen, rufen Sie CCmdTarget::EnableAggregation im Konstruktor des Objekts auf.

Wenn das Objekt auch Aggregate verwendet, müssen Sie zudem sicherstellen, das richtige "controlling unknown"-Objekt an die Aggregatobjekte zu übergeben. Normalerweise wird dieser IUnknown-Zeiger an das Objekt übergeben, wenn das Aggregat erstellt wird. Beispielsweise ist der pUnkOuter-Parameter der "controlling unknown"-Zeiger für Objekte, die mit CoCreateInstance erstellt werden. Der richtige "controlling unknown"-Zeiger kann durch Aufrufen von CCmdTarget::GetControllingUnknown abgerufen werden. Der Wert, der von dieser Funktion zurückgegeben wird, ist jedoch während des Konstruktors ungültig. Aus diesem Grund wird vorgeschlagen, die Aggregate nur in einer Überschreibung von CCmdTarget::OnCreateAggregates zu erstellen, bei der der Rückgabewert von GetControllingUnknown zuverlässig ist, auch wenn er von der COleObjectFactory-Implementierung erstellt wird.

Wichtig ist auch, dass das Objekt den richtigen Verweiszähler bearbeitet, wenn künstliche Verweiszähler hinzugefügt oder freigegeben werden. Um dies sicherzustellen, rufen Sie immer ExternalAddRef und ExternalRelease statt InternalRelease und InternalAddRef auf. Es passiert nur selten, dass InternalRelease oder InternalAddRef für eine Klasse aufgerufen werden, die Aggregation unterstützt.

Reference Material

Die erweiterte Verwendung von OLE, wie Definieren eigener Schnittstellen oder Überschreiben der Implementierung des Frameworks der OLE-Schnittstellen, erfordert die Nutzung des zugrunde liegenden Schnittstellenzuordnungsmechanismus.

In diesem Abschnitt werden alle Makros und APIs erläutert, die zum Implementieren dieser erweiterten Funktionen verwendet werden.

CCmdTarget::EnableAggregation – Funktionsbeschreibung

void EnableAggregation();

Hinweise

Rufen Sie diese Funktion im Konstruktor der abgeleiteten Klasse auf, wenn Sie OLE-Aggregation für Objekte dieses Typs unterstützen möchten. Dadurch wird eine besondere IUnknown-Implementierung vorbereitet, die für aggregatfähige Objekte erforderlich ist.

CCmdTarget::ExternalQueryInterface – Funktionsbeschreibung

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

Parameter

lpIID
Ein ferner Zeiger auf eine IID (das erste Argument für QueryInterface)

ppvObj
Ein Zeiger auf ein IUnknown*-Objekt (das zweite Argument für QueryInterface)

Hinweise

Rufen Sie diese Funktion in der Implementierung von IUnknown für jede Schnittstelle auf, die Ihre Klasse implementiert. Diese Funktion stellt die datengesteuerte Standardimplementierung von QueryInterface auf Grundlage der Schnittstellenzuordnung des Objekts bereit. Es ist erforderlich, den Rückgabewert in ein HRESULT umzuwandeln. Wenn das Objekt aggregiert wird, ruft diese Funktion das "controlling IUnknown"-Objekt auf, anstatt die lokale Schnittstellenzuordnung zu verwenden.

CCmdTarget::ExternalAddRef – Funktionsbeschreibung

DWORD ExternalAddRef();

Hinweise

Rufen Sie diese Funktion in der Implementierung von IUnknown::AddRef für jede Schnittstelle auf, die Ihre Klasse implementiert. Der Rückgabewert ist der neue Verweiszähler für das CCmdTarget-Objekt. Wenn das Objekt aggregiert wird, ruft diese Funktion das "controlling IUnknown"-Objekt auf, anstatt den lokalen Verweiszähler zu bearbeiten.

CCmdTarget::ExternalRelease – Funktionsbeschreibung

DWORD ExternalRelease();

Hinweise

Rufen Sie diese Funktion in der Implementierung von IUnknown::Release für jede Schnittstelle auf, die Ihre Klasse implementiert. Der Rückgabewert gibt den neuen Verweiszähler für das Objekt an. Wenn das Objekt aggregiert wird, ruft diese Funktion das "controlling IUnknown"-Objekt auf, anstatt den lokalen Verweiszähler zu bearbeiten.

DECLARE_INTERFACE_MAP – Makrobeschreibung

DECLARE_INTERFACE_MAP

Hinweise

Verwenden Sie dieses Makro in einer Klasse, die von CCmdTarget abgeleitet ist und über eine Schnittstellenzuordnung verfügt. Wird auf die gleiche Weise wie DECLARE_MESSAGE_MAP verwendet. Dieser Makroaufruf sollte in die Klassendefinition, normalerweise in einer Headerdatei (.H), platziert werden. Eine Klasse mit DECLARE_INTERFACE_MAP muss die Schnittstellenzuordnung in der Implementierungsdatei (. CPP) mit den makros BEGIN_INTERFACE_MAP und END_INTERFACE_MAP.

BEGIN_INTERFACE_PART und END_INTERFACE_PART – Makrobeschreibungen

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

Parameter

localClass
Der Name der Klasse, die die Schnittstelle implementiert

iface
Der Name der Schnittstelle, die diese Klasse implementiert

Hinweise

Für jede Schnittstelle, die Ihre Klasse implementiert, müssen Sie über ein BEGIN_INTERFACE_PART- und END_INTERFACE_PART-Paar verfügen. Diese Makros definieren eine lokale Klasse, die von der von Ihnen definierten OLE-Schnittstelle abgeleitet wird, sowie eine eingebettete Membervariable dieser Klasse. Die Elemente "AddRef", "Release" und "QueryInterface " werden automatisch deklariert. Sie müssen die Deklarationen für die anderen Memberfunktionen einschließen, die Teil der implementierten Schnittstelle sind (diese Deklarationen werden zwischen dem BEGIN_INTERFACE_PART und END_INTERFACE_PART Makros platziert).

Das iface-Argument ist die OLE-Schnittstelle, die Sie implementieren möchten, z IAdviseSink. B. oder (oder IPersistStorage Ihre eigene benutzerdefinierte Schnittstelle).

Das argument localClass ist der Name der lokalen Klasse, die definiert wird. Der Buchstabe "x" wird dem Namen automatisch vorangestellt. Diese Namenskonvention wird verwendet, um Konflikte mit gleichnamigen globalen Klassen zu vermeiden. Darüber hinaus entspricht der Name des eingebetteten Elements dem localClass-Namen, mit ausnahme der Präfix "m_x".

Beispiel:

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)

würde eine lokale Klasse mit dem Namen XMyAdviseSink definieren, der von IAdviseSink abgeleitet wurde, und ein Member der Klasse, in der es als m_xMyAdviseSink.Note deklariert wird:

Hinweis

Die Zeilen, die mit STDMETHOD_ beginnen, werden im Wesentlichen aus OLE2 kopiert. H und leicht geändert. Durch Kopieren aus OLE2.H lassen sich Fehler reduzieren, die andernfalls schwer zu beheben sind.

BEGIN_INTERFACE_MAP und END_INTERFACE_MAP – Makrobeschreibungen

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

Parameter

theClass
Die Klasse, in der die Schnittstellenzuordnung definiert werden soll

Baseclass
Die Klasse, von der die Klasse abgeleitet wird.

Hinweise

Die BEGIN_INTERFACE_MAP- und END_INTERFACE_MAP-Makros werden in der Implementierungsdatei verwendet, um die Schnittstellenzuordnung tatsächlich zu definieren. Für jede implementierte Schnittstelle gibt es einen oder mehrere INTERFACE_PART Makroaufrufe. Für jedes Aggregat, das die Klasse verwendet, gibt es einen INTERFACE_AGGREGATE Makroaufruf.

INTERFACE_PART – Makrobeschreibung

INTERFACE_PART(theClass, iid, localClass)

Parameter

theClass
Der Name der Klasse, die die Schnittstellenzuordnung enthält.

Iid
Die IID, die der eingebetteten Klasse zugeordnet werden soll.

localClass
Der Name der lokalen Klasse (ohne das "X").

Hinweise

Dieses Makro wird zwischen dem BEGIN_INTERFACE_MAP-Makro und dem END_INTERFACE_MAP Makro für jede Schnittstelle verwendet, die ihr Objekt unterstützt. Damit können Sie eine IID einem Element der Klasse zuordnen, die durch die Klasse und die localClass angegeben ist. Die "m_x" wird der localClass automatisch hinzugefügt. Beachten Sie, dass maximal eine IID mit einem einzigen Member zugeordnet werden kann. Dies ist hilfreich, wenn Sie nur eine "am stärksten abgeleitete" Schnittstelle implementieren und alle Zwischenschnittstellen ebenfalls bereitstellen möchten. Ein gutes Beispiel hierfür ist die IOleInPlaceFrameWindow-Schnittstelle. Die Hierarchie sieht wie folgt aus:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Wenn ein Objekt implementiert wird IOleInPlaceFrameWindow, kann QueryInterface ein Client auf einer der folgenden Schnittstellen zugreifen: IOleUIWindow, , IOleWindowoder IUnknown, neben der "abgeleiteten" Schnittstelle IOleInPlaceFrameWindow (die, die Sie tatsächlich implementieren). Um dies zu behandeln, können Sie mehrere INTERFACE_PART Makros verwenden, um die einzelnen Basisschnittstellen der IOleInPlaceFrameWindow Schnittstelle zuzuordnen:

in der Klassendefinitionsdatei:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

in der Klassenimplementierungsdatei:

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

Das Framework berücksichtigt IUnknown, da es immer erforderlich ist.

INTERFACE_PART – Makrobeschreibung

INTERFACE_AGGREGATE(theClass, theAggr)

Parameter

theClass
Der Name der Klasse, die die Schnittstellenzuordnung enthält,

theAggr
Der Name der Membervariable, die aggregiert werden soll.

Hinweise

Dieses Makro wird verwendet, um dem Framework mitzuteilen, dass die Klasse ein Aggregatobjekt verwendet. Sie muss zwischen den makros BEGIN_INTERFACE_PART und END_INTERFACE_PART angezeigt werden. Ein Aggregatobjekt ist ein separates Objekt, das von IUnknown abgeleitet wird. Mithilfe eines Aggregats und des INTERFACE_AGGREGATE-Makros können Sie festlegen, dass alle Schnittstellen, die vom Aggregat unterstützt werden, direkt vom Objekt unterstützt werden. Das Argument "Aggr " ist einfach der Name einer Membervariable ihrer Klasse, die von IUnknown abgeleitet wird (entweder direkt oder indirekt). Alle INTERFACE_AGGREGATE Makros müssen den INTERFACE_PART Makros folgen, wenn sie in einer Schnittstellenzuordnung platziert werden.

Siehe auch

Technische Hinweise – nach Nummern geordnet
Technische Hinweise – nach Kategorien geordnet