Partager via


TN038 : Implémentation MFC/OLE IUnknown

Notes

La note technique suivante n'a pas été mise à jour depuis son inclusion initiale dans la documentation en ligne.Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrectes.Pour obtenir les informations les plus récentes, il est recommandé de rechercher l'objet qui vous intéresse dans l'index de la documentation en ligne.

Au cœur OLE 2 se trouve le « modèle d'objet de composant OLE », ou COM. COM définit une norme définissant la manière avec laquelle les objets communiquent les uns avec les autres. Cela inclut les détails sous-jacents de l'aspect d'un « objet », notamment les méthodes de distribution sur un objet. COM définit également une classe de base, dont dérivent toutes les classes COM compatibles. Cette classe de base est IUnknown. Bien que l'interface IUnknown soit indiquée comme une classe C++, COM n'est pas spécifique à un langage particulier — il peut être implémenté en C, en PASCAL ou en tout autre langage qui prend en charge la disposition binaire d'un objet COM.

OLE fait référence à toutes les classes dérivées de IUnknown en tant qu'« interfaces ». Cette distinction est importante, car une « interface » comme IUnknown n'est associée à aucune implémentation. Elle définit simplement le protocole par lequel les objets communiquent, pas les détails de ce que ces implémentations exécutent. C'est utile pour un système qui permet une flexibilité maximale. Il incombe à MFC d'implémenter un comportement par défaut pour les programmes MFC/C++.

Pour comprendre l'implémentation MFC IUnknown vous devez d'abord comprendre ce qu'est cette interface. Une version simplifiée de IUnknown est définie ci-dessous :

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

Notes

Certains détails de convention d'appel nécessaires, tels que __stdcall sont exclus pour cette illustration.

Les fonctions membres AddRef et Release contrôlent la gestion de la mémoire de l'objet. COM utilise un modèle de décompte de référence pour le suivi des objets. Un objet n'est jamais directement référencé comme dans C++. À la place, les objets COM sont toujours référencés par l'intermédiaire d'un pointeur. Pour libérer l'objet lorsque le propriétaire n'en a plus besoin, le membre Release de l'objet est appelé (plutôt que d'utiliser l'opérateur de suppression, comme cela est fait pour un objet traditionnel C++). Le mécanisme de décompte de références propose de nombreuses références pour un objet unique à gérer. Une implémentation de AddRef et de Release gère un compte de référence sur l'objet — l'objet n'est pas supprimé tant que son compte de référence n'atteint pas zéro.

AddRef et Release sont assez simples en termes d'implémentation. Voici une implémentation simple :

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

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

La fonction membre QueryInterface est un peu plus intéressante. Il n'est pas très intéressant d'avoir un objet dont les seules fonctions membres sont AddRef et Release. Il serait utile que l'objet d'effectuer d'autres tâches que celles fournies par IUnknown. C'est là que QueryInterface est utile. Cela vous permet d'obtenir une « interface » différente sur le même objet. Ces interfaces sont généralement dérivées de IUnknown et ajoutent des fonctionnalités supplémentaires en ajoutant de nouvelles fonctions membres. Les interfaces COM n'ont jamais de variables membres déclarées dans l'interface, et toutes les fonctions membres sont déclarées comme virtuelles pures. Par exemple :

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

Pour obtenir un IPrintInterface si vous avez uniquement IUnknown, appelez QueryInterface à l'aide de IID IPrintInterface. Un IID est un nombre de 128 bits qui identifie l'interface de manière unique. Il existe un IID pour chaque interface que vous ou OLE définit. Si pUnk est un pointeur vers un objet IUnknown, le code pour récupérer un IPrintInterface à partir de celui-ci peut être :

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

Cela semble relativement simple, mais comment implémenteriez-vous un objet qui prend en charge les interfaces IPrintInterface et IUnknown ? Dans ce cas il est simple car IPrintInterface est directement dérivé d'IUnknown ; en implémentant IPrintInterface, IUnknown est automatiquement pris en charge. Par exemple :

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

Les implémentations de AddRef et de Release sont exactement les mêmes que celles implémentées ci-dessus. CPrintObj::QueryInterface ressemblerait à ceci :

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

Comme vous pouvez le voir, si l'identificateur d'interface (IID) est reconnu, un pointeur est retourné vers votre objet ; sans cela une erreur se produit. Notez également qu'une QueryInterface réussie produit une AddRef implicite. Naturellement, vous devez également implémenter CEditObj::Print. Cette procédure est simple car IPrintInterface était directement dérivé de l'interface IUnknown. Toutefois, si vous souhaitiez prendre en charge deux interfaces différentes, toutes deux dérivées de IUnknown, tenez compte de ce qui suit :

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

Bien qu'il existe un certain nombre de différentes façons d'implémenter une classe prenant en charge à la fois IEditInterface et IPrintInterface, y compris l'héritage multiple C++, cette note se concentre sur l'utilisation de classes imbriquées pour implémenter cette fonctionnalité.

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

L'ensemble de l'implémentation est incluse ci-dessous :

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

Notez que la majeure partie de l'implémentation IUnknown est placée dans la classe CEditPrintObj au lieu de dupliquer le code dans CEditPrintObj::CEditObj et CEditPrintObj::CPrintObj. Cela réduit la quantité de code et évite des bogues. Le point clé ici c'est qu'à partir de l'interface IUnknown il est possible d'appeler QueryInterface pour récupérer n'importe quelle interface que l'objet peut prendre en charge, et à partir de chacune de ces interfaces il est possible d'effectuer la même opération. Cela signifie que toutes les fonctions QueryInterface disponibles dans chaque interface doivent se comporter exactement de la même façon. Pour que ces objets incorporés appellent l'implémentation dans l'« objet externe », un pointeur arrière est utilisé (m_pParent). Le pointeur m_pParent est initialisé pendant le constructeur CEditPrintObj. Vous implémentez CEditPrintObj::CPrintObj::PrintObject et CEditPrintObj::CEditObj::EditObject également. Un bit de code a été ajouté pour inclure une fonctionnalité permettant de modifier l'objet. Heureusement, il est assez rare que les interfaces disposent d'une seule fonction membre (bien que cela soit possible) et dans ce cas, EditObject et PrintObject sont généralement regroupés dans une interface unique.

Cela représente beaucoup d'explications et beaucoup de code pour un scénario aussi simple. Les classes MFC/OLE offrent une alternative plus simple. L'implémentation MFC utilise une technique similaire à la façon dont les messages Windows sont encapsulés avec les tables des messages. Cette fonctionnalité est appelée Mappages d'interface et est décrite dans la section suivante.

Mappages d'interface MFC

MFC/OLE inclut une implémentation de « mappages d'interface » semblable aux « tables de messages » et aux « tables de dispatch » de MFC dans le concept et l'exécution. Les principales fonctionnalités des mappages d'interface MFC sont les suivantes :

  • Implémentation standard de IUnknown, générée dans la classe CCmdTarget.

  • Maintenance du décompte de références, modifié par AddRef et Release

  • Implémentation pilotée par les données de QueryInterface

En outre, les mappages d'interface prennent en charge les fonctionnalités avancées suivantes :

  • Prise en charge pour la création d'objets COM pouvant être regroupés en agrégats

  • Prise en charge pour l'utilisation d'objets d'agrégation dans l'implémentation d'un objet COM

  • L'implémentation est hookable et extensible

Pour plus d'informations sur l'agrégation, consultez la rubrique Agrégation.

La prise en charge du mappage d'interface MFC est enracinée dans la classe CCmdTarget. CCmdTarget « a un » décompte de référence ainsi que toutes les fonctions membres associées à l'implémentation de IUnknown (le décompte de référence par exemple est dans CCmdTarget). Pour créer une classe qui prend en charge OLE COM, dérivez une classe de CCmdTarget et utilisez plusieurs macros ainsi que des fonctions membres de CCmdTarget pour implémenter les interfaces souhaitées. L'implémentation de MFC utilise des classes imbriquées pour définir chaque implémentation d'interface, en grande partie comme dans l'exemple ci-dessus. Cela est facilité par une implémentation standard d'IUnknown ainsi que d'un certain nombre de macros qui éliminent une partie du code répétitif.

Pour implémenter une classe à l'aide des mappages d'interface MFC

  1. Dérivez une classe directement ou indirectement depuis CCmdTarget.

  2. Utilisez la fonction DECLARE_INTERFACE_MAP dans la définition de classe dérivée.

  3. Pour chaque interface que vous souhaitez prendre en charge, utilisez les macros BEGIN_INTERFACE_PART et END_INTERFACE_PART dans la définition de classe.

  4. Dans le fichier d'implémentation, utilisez les macros BEGIN_INTERFACE_MAP et END_INTERFACE_MAP pour définir le mappage d'interfaces de la classe.

  5. Pour chaque IID pris en charge, utilisez la macro INTERFACE_PART entre les macros BEGIN_INTERFACE_MAP et END_INTERFACE_MAP pour mapper cet IID à une « partie » spécifique de votre classe.

  6. Implémentez chacune des classes imbriquées qui représentent les interfaces prises en charge.

  7. Utilisez cette macro pour METHOD_PROLOGUE pour accéder au parent, un objet dérivé de CCmdTarget.

  8. AddRef, Release et QueryInterface peuvent déléguer à CCmdTarget l'implémentation de ces fonctions (ExternalAddRef, ExternalRelease et ExternalQueryInterface).

L'exemple CPrintEditObj ci-dessus peut être implémenté comme suit :

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

La déclaration ci-dessus crée une classe dérivée de CCmdTarget. La macro DECLARE_INTERFACE_MAP indique à l'infrastructure que cette classe aura un mappage d'interfaces personnalisé. En outre, les macros BEGIN_INTERFACE_PART et END_INTERFACE_PART définissent les classes imbriquées, dans ce cas avec les noms CEditObj et CPrintObj (le X est utilisé uniquement pour différencier les classes imbriquées des classes globales qui commencent par « C » et des classes d'interface qui commencent par « I »). Deux membres imbriqués de ces classes sont créés : m_CEditObj et m_CPrintObj, respectivement. Les macros déclarent automatiquement les fonctions AddRef, Release et QueryInterface ; donc vous devez uniquement déclarer les fonctions spécifiques à cette interface : EditObject et PrintObject (la macro OLE STDMETHOD est utilisée afin que _stdcall et les mots clés virtuels soient fournis correctement pour la plateforme cible).

Pour implémenter le mappage d'interface pour cette classe :

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

Cela connecte respectivement IID_IPrintInterface IID avec m_CPrintObj et IID_IEditInterface avec le m_CEditObj. L'implémentation CCmdTarget de QueryInterface (CCmdTarget::ExternalQueryInterface) utilise ce mappage pour retourner des pointeurs vers m_CPrintObj et au m_CEditObj sur demande. Il n'est pas nécessaire d'inclure une entrée pour IID_IUnknown ; l'infrastructure utilise la première interface du mappage (dans ce cas, m_CPrintObj) lorsque IID_IUnknown est demandé.

Bien que la macro BEGIN_INTERFACE_PART ait automatiquement déclaré les fonctions AddRef, Release et QueryInterface, il vous reste tout de même à les implémenter :

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

L'implémentation pour CEditPrintObj::CPrintObj, ressemblera aux définitions ci-dessus pour CEditPrintObj::CEditObj. Bien qu'il serait possible de créer une macro qui puisse être utilisée pour générer automatiquement ces fonctions (mais c'était précédemment le cas dans le développement de MFC/OLE), il devient difficile de définir des points d'arrêt lorsqu'une macro génère plusieurs lignes de code. Pour cette raison, ce code est développé manuellement.

En utilisant l'implémentation d'infrastructure des mappages de messages, plusieurs choses n'étaient pas nécessaires :

  • Implémenter QueryInterface

  • Implémenter AddRef et Release

  • Déclarez l'une ou l'autre de ces méthodes intégrées sur vos deux interfaces

En outre, l'infrastructure utilise des tables de messages en interne. Cela vous permet de dériver d'une classe d'infrastructure (COleServerDoc, par exemple) qui prend déjà en charge certaines interfaces et fournit des remplacements ou des ajouts aux interfaces fournies par l'infrastructure. Vous pouvez le faire parce que l'infrastructure prend entièrement en charge l'héritage d'un mappage d'interface à partir d'une classe de base. C'est la raison pour laquelle BEGIN_INTERFACE_MAP prend comme second paramètre le nom de la classe de base.

Notes

Il n'est généralement pas possible de réutiliser l'implémentation des implémentations intégrées des interfaces OLE de MFC en héritant simplement la spécialisation incorporée de cette interface de la version MFC.Ce n'est pas possible parce que la macro METHOD_PROLOGUE pour obtenir un accès à CCmdTargetcontenant un objet dérivé implique un offset fixe de l'objet incorporé à partir de l'objet dérivé de CCmdTarget.Cela signifie, par exemple, que vous ne pouvez pas dériver un XMyAdviseSink incorporé de l'implémentation MFC dans COleClientItem::XAdviseSink, car XAdviseSink repose sur un offset spécifique à partir du haut de l'objet COleClientItem.

Notes

Vous pouvez, toutefois, déléguer à l'implémentation MFC pour l'ensemble des fonctions pour lesquelles vous voulez le comportement par défaut de MFC.Cela s'effectue dans l'implémentation MFC de IOleInPlaceFrame (XOleInPlaceFrame) dans la classe COleFrameHook (il délègue à m_xOleInPlaceUIWindow pour de nombreuses fonctions).Cette conception a été choisie pour réduire la taille d'exécution des objets qui implémentent un grand nombre d'interfaces ; elle élimine le besoin d'avoir un pointeur arrière (la façon dont m_pParent a été utilisé dans la section précédente).

Agrégation et mappages d'interface

Outre la prise en charge des objets COM autonomes, MFC prend également en charge l'agrégation. En soi l'agrégation est un sujet trop complexe à discuter ici ; consultez la rubrique Agrégation pour plus d'informations à ce sujet. Cette remarque décrira simplement la prise en charge pour l'agrégation intégrée dans le framework et les mappages d'interface.

Il existe deux façons d'utiliser l'agrégation : (1) à l'aide d'un objet COM qui prend en charge l'agrégation, et (2) en implémentant un objet qui peut être agrégé par un autre. Ces fonctions peuvent être désignées comme « utilisant un objet agrégé » et « rendant un objet utilisable dans une agrégation ». MFC prend en charge les deux.

Utilisation d'un objet d'agrégation

Pour utiliser un objet d'agrégation, il faut attacher l'agrégat dans le mécanisme QueryInterface. En d'autres termes, l'objet d'agrégation doit se comporter comme s'il s'agissait d'une partie native de votre objet. Comment l'agrégat est-il lié au mécanisme de mappage d'interfaces de MFC ? Outre la macro INTERFACE_PART, où un objet imbriqué est mappé à un IID, vous pouvez également déclarer un objet d'agrégation dans le cadre de votre classe dérivée CCmdTarget. Pour cela, la macro INTERFACE_AGGREGATE est utilisée. Cela vous permet de spécifier une variable membre (qui doit être un pointeur désignant IUnknown ou une classe dérivée), qui doit être intégrée dans le mécanisme de mappage d'interfaces. Si le pointeur n'a pas la valeur NULL lorsque CCmdTarget::ExternalQueryInterface est appelé, l'infrastructure appelle automatiquement la fonction membre QueryInterface de l'objet d'agrégation, si l'IID demandé ne fait pas partie des IID natifs pris en charge par l'objet CCmdTarget proprement dit.

Pour utiliser la macro INTERFACE_AGGREGATE

  1. Déclarez une variable membre (un IUnknown*) qui contiendra un pointeur vers l'objet d'agrégat.

  2. Incluez une macro INTERFACE_AGGREGATE dans votre mappage d'interfaces, qui fait référence à la variable membre par nom.

  3. À un moment ou à un autre (généralement pendant CCmdTarget::OnCreateAggregates), initialisez la variable membre une valeur autre que NULL.

Par exemple :

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

La variable m_lpAggrInner est initialisée sur une valeur NULL dans le constructeur. L'infrastructure ignore une variable membre null dans l'implémentation par défaut de QueryInterface. OnCreateAggregates est un emplacement idéal pour créer réellement vos objets d'agrégation. Vous devez l'appeler explicitement si vous créez l'objet en dehors de l'implémentation MFC de COleObjectFactory. La raison de la création des agrégats dans CCmdTarget::OnCreateAggregates ainsi que de l'utilisation de CCmdTarget::GetControllingUnknown devient visible lorsque la création des objets pouvant être regroupé en agrégats est abordée.

Cette technique donne à votre objet toutes les interfaces que l'objet d'agrégation prend en charge ainsi que ses interfaces natives. Si vous souhaitez uniquement un sous-ensemble des interfaces que l'agrégat prend en charge, vous pouvez remplacer CCmdTarget::GetInterfaceHook. Cela vous offre une hookability de très bas niveau, semblable à QueryInterface. En général, vous souhaitez toutes les interfaces que l'agrégat prend en charge.

Rendre une implémentation d'objets utilisable dans une agrégation

Pour qu'un objet soit utilisable dans une agrégation, l'implémentation d'AddRef, Release et QueryInterface doit déléguer à un « contrôle d'Unknown ». En d'autres termes, pour faire partie de l'objet, elle doit déléguer AddRef, Release et QueryInterface à un autre objet, également dérivé de IUnknown. Ce « contrôle de l'inconnu » est fourni à l'objet lorsqu'il est créé, c'est-à-dire qu'il est fourni à l'implémentation de COleObjectFactory. Son implémentation implique une petite surcharge, et n'est pas souhaitable dans certains cas, par conséquent, MFC en fait une option facultative. Pour permettre à un objet d'être utilisable dans une agrégation, vous devez appeler CCmdTarget::EnableAggregation à partir du constructeur de l'objet.

Si l'objet utilise également des agrégats, vous devez également veiller à passer le « contrôle d'Unknown » approprié aux objets d'agrégation. Généralement ce pointeur IUnknown est passé à l'objet lorsque l'agrégat est créé. Par exemple, le paramètre pUnkOuter est le « contrôle d'Unknown » pour les objets créés avec CoCreateInstance. Le pointeur « contrôle d'Unknown » correct peut être récupéré en appelant CCmdTarget::GetControllingUnknown. La valeur retournée par cette fonction, elle n'est toutefois pas valide pendant le constructeur. Pour cette raison, nous vous recommandons de créer vos agrégats uniquement dans une substitution de CCmdTarget::OnCreateAggregates, où la valeur de retour de GetControllingUnknown est fiable, même si elle a été créée à partir de l'implémentation de COleObjectFactory.

Il est également important que l'objet manipule le décompte de références correct en ajoutant ou en libérant des décomptes de références artificiels. Pour être sûr que cette opération est effectuée, appelez toujours ExternalAddRef et ExternalRelease au lieu de InternalRelease et InternalAddRef. Il est rare d'appeler InternalRelease ou InternalAddRef sur une classe qui prend en charge l'agrégation.

Documents de référence

L'utilisation avancée d'OLE, comme définir vos propres interfaces ou substituer l'implémentation de l'infrastructure des interfaces OLE, nécessite l'utilisation du mécanisme de mappage d'interfaces sous-jacent.

Cette section traite chaque macro et les API utilisées pour implémenter cette fonctionnalité avancée.

CCmdTarget::EnableAggregation — description de fonction

void EnableAggregation();

Remarques

Appelez cette fonction dans le constructeur de la classe dérivée si vous souhaitez prendre en charge l'agrégation OLE pour les objets de ce type. Cela prépare une implémentation particulière d'IUnknown requise pour les objets pouvant être regroupés en agrégats.

CCmdTarget::ExternalQueryInterface — description de fonction

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

Remarques

Paramètres

  • lpIID
    Pointeur lointain à un IID (le premier argument de QueryInterface)

  • ppvObj
    Pointeur vers un IUnknown* (deuxième argument de QueryInterface)

Remarques

Appelez cette fonction dans votre implémentation d'IUnknown pour chaque interface que votre classe implémente. Cette fonction fournit l'implémentation pilotée par des données standard QueryInterface selon le mappage d'interface de votre objet. Il est nécessaire de caster la valeur de retour à un HRESULT. Si l'objet est agrégé, cette fonction appelle le « contrôle d'IUnknown » au lieu d'utiliser le mappage d'interface local.

CCmdTarget::ExternalAddRef — description de fonction

DWORD ExternalAddRef();

Remarques

Appelez cette fonction dans votre implémentation d'IUnknown::AddRef pour chaque interface que votre classe implémente. La valeur de retour est le nouveau décompte de références sur l'objet CCmdTarget. Si l'objet est agrégé, cette fonction appelle le « contrôle d'IUnknown » au lieu de manipuler le décompte de références locales.

CCmdTarget::ExternalRelease — description de fonction

DWORD ExternalRelease();

Remarques

Appelez cette fonction dans votre implémentation d'IUnknown::Release pour chaque interface que votre classe implémente. La valeur de retour indique le nouveau décompte de références de l'objet. Si l'objet est agrégé, cette fonction appelle le « contrôle d'IUnknown » au lieu de manipuler le décompte de références locales.

DECLARE_INTERFACE_MAP — Description de macro

DECLARE_INTERFACE_MAP

Remarques

Utilisez cette macro dans toute classe dérivée de CCmdTarget qui possède un mappage d'interfaces. Utilisé à peu près de la même façon que DECLARE_MESSAGE_MAP. Cet appel de macro doit être placé dans la définition de classe, habituellement dans un fichier en-tête (. H). Une classe avec DECLARE_INTERFACE_MAP doit définir le mappage d'interface dans le fichier d'implémentation (.CPP) avec les macros BEGIN_INTERFACE_MAP et END_INTERFACE_MAP.

BEGIN_INTERFACE_PART et END_INTERFACE_PART — Descriptions macro

BEGIN_INTERFACE_PART( 
   localClass,
   iface 
);
END_INTERFACE_PART( 
   localClass 
)

Remarques

Paramètres

  • localClass
    Nom de la classe qui implémente l'interface

  • iface
    Nom de l'interface que cette classe implémente

Remarques

Pour chaque interface que votre classe implémente, une paire BEGIN_INTERFACE_PART et END_INTERFACE_PART est requise. Ces macros définissent une classe locale dérivée de l'interface OLE que vous définissez ainsi qu'une variable membre incorporée de cette classe. Les membres AddRef, Release et QueryInterface sont déclarés automatiquement. Vous devez inclure les déclarations des autres fonctions membres qui font partie de l'interface implémentée (ces déclarations sont placées entre les macros BEGIN_INTERFACE_PART et END_INTERFACE_PART ).

L'argument iface est l'interface OLE que vous souhaitez implémenter, tel que IAdviseSink, ou IPersistStorage (ou votre propre interface personnalisée).

L'argument localClass est le nom de la classe locale qui sera définie. Un « x » sera automatiquement ajouté au nom. Cette convention d'affectation de noms est utilisée pour éviter des collisions avec les classes globales du même nom. En outre, le nom du membre incorporé est identique au nom localClass sauf qu'il est précédé de « m_x ».

Par exemple :

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)

définirait une classe locale appelée XMyAdviseSink dérivée de IAdviseSink, et un membre de la classe dans laquelle il est déclaré appelée m_xMyAdviseSink.Note :

Notes

Les lignes commençant par STDMETHOD_ sont essentiellement copiées à partir d'OLE2.H et légèrement modifiées.Leur copie depuis OLE2.H peut réduire les erreurs qui sont difficiles à résoudre.

BEGIN_INTERFACE_MAP et END_INTERFACE_MAP — Descriptions macro

BEGIN_INTERFACE_MAP( 
   theClass,
   baseClass 
) 
END_INTERFACE_MAP

Remarques

Paramètres

  • theClass
    Classe dans laquelle le mappage d'interface doit être défini

  • baseClass
    La classe dont theClass dérive.

Remarques

Les macros BEGIN_INTERFACE_MAP et END_INTERFACE_MAP sont utilisées dans le fichier d'implémentation pour définir réellement le mappage d'interfaces. ,Pour chaque interface implémentée, un ou plusieurs appels de macro INTERFACE_PART sont effectués. Pour chaque agrégat que la classe utilise, un appel de macro INTERFACE_AGGREGATE est effectué.

INTERFACE_PART — Description macro

INTERFACE_PART( 
   theClass,
   iid, 
   localClass 
)

Remarques

Paramètres

  • theClass
    Nom complet de la classe qui contient le mappage d'interface.

  • iid
    IID qui doit être mappé à la classe incorporée.

  • localClass
    Nom de la classe locale (moins le « X »).

Remarques

Cette macro est utilisée entre la macro BEGIN_INTERFACE_MAP et la macro END_INTERFACE_MAP pour chaque interface que votre objet prendra en charge. Elle vous permet de mapper un IID à un membre de la classe indiquée par theClass et localClass. « m_x » sera ajouté à localClass automatiquement. Notez que plusieurs IID peut être associés à un seul membre. Cela s'avère utile lorsque vous implémentez uniquement l'interface « la plus dérivée » et souhaitez fournir toutes les interfaces intermédiaires également. Un bon exemple en est l'interface IOleInPlaceFrameWindow. Sa hiérarchie se présente comme suit :

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Si un objet implémente IOleInPlaceFrameWindow, un client peut QueryInterface sur l'une des interfaces suivantes : IOleUIWindow, IOleWindow ou IUnknown, en plus de l'interface « la plus dérivée » IOleInPlaceFrameWindow (celle que vous implémentez réellement). Pour gérer cela, vous pouvez utiliser plusieurs macro INTERFACE_PART pour mapper chaque interface de base à l'interface IOleInPlaceFrameWindow :

dans le fichier de définition de classe :

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

dans le fichier d'implémentation de classe :

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

L'infrastructure se charge de l'interface IUnknown car elle est toujours requise.

INTERFACE_PART — Description macro

INTERFACE_AGGREGATE( 
   theClass,
   theAggr 
)

Remarques

Paramètres

  • theClass
    Nom complet de la classe qui contient le mappage d'interface,

  • theAggr
    Le nom de la variable membre qui doit être agrégé.

Remarques

Cette macro est utilisée pour indiquer à l'infrastructure que la classe utilise un objet d'agrégation. Elle doit apparaître entre les macros BEGIN_INTERFACE_PART et END_INTERFACE_PART. Un objet d'agrégat est un objet distinct, dérivé de IUnknown. En utilisant un agrégat et la macro INTERFACE_AGGREGATE, vous pouvez faire en sorte que toutes les interfaces prises en charge par l'agrégat semblent l'être directement par l'objet. L'argument theAggr est simplement le nom d'une variable membre de votre classe dérivée de IUnknown (directement ou indirectement). Toutes les macros INTERFACE_AGGREGATE doivent suivre les macros INTERFACE_PART une fois placées dans un mappage d'interfaces.

Voir aussi

Autres ressources

Notes techniques de nombres

notes techniques de catégorie