Héritage et interfaces

De RAD Studio
Aller à : navigation, rechercher

Remonter à Modèles C++ et Pascal Objet


A la différence du C++, le langage Pascal Objet ne supporte pas l'héritage multiple. Toute classe créée en ayant un ancêtre RTL hérite de cette restriction. Vous ne pouvez donc pas utiliser plusieurs classes de base pour une classe C++ de style RTL, même si la classe VCL n'est pas l'ancêtre immédiat.

Utilisation d'interfaces à la place de l'héritage multiple

Dans la plupart des situations où vous utiliseriez l'héritage multiple en C++, le code Pascal Objet utilise des interfaces à la place. Il n'y a pas de construction C++ correspondant directement au concept d'interface Pascal Objet. Une interface Pascal Objet se comporte comme une classe sans implémentation. Une interface ressemble donc à une classe sans données membre et dont toutes les méthodes sont purement virtuelles. Si une classe Pascal Objet ne peut avoir qu'une seule classe parent, elle peut gérer plusieurs interfaces. Il est possible dans du code Pascal Objet d'affecter une instance de classe à une variable du type d'une de ces interfaces, tout comme vous affectez une instance de classe à une variable ayant le type d'une des classes ancêtres. Cela permet un comportement polymorphique dans des classes qui partagent la même interface, même si elles n'ont pas d'ancêtre commun.

Le compilateur C++Builder reconnaît les classes n'ayant que des méthodes virtuelles pures et pas de données membres comme des interface Pascal Objet. Ainsi, quand vous créez une classe de style VCL, vous pouvez utiliser l'héritage multiple seulement si toutes les classes de base, sauf celles de style RTL, n'ont pas de données membres et uniquement des méthodes virtuelles pures.

Remarque : Les classes interface n'ont pas besoin d'être des classes de style VCL, la seule contrainte c'est qu'elles n'aient pas de données membres et uniquement des méthodes virtuelles pures.

Déclaration des classes interfaces

Vous déclarez une classe représentant une interface comme toute autre classe C++. Néanmoins, en utilisant certaines conventions, vous pouvez expliciter que cette classe doit agir comme une interface. Ces conventions sont :

  • Au lieu d'utiliser le mot clé class, les interfaces sont déclarées en utitilisant __interface. __interface est une macro qui correspond au mot clé class. Il n'est pas nécessaire, mais il rend explicite que cette classe doit agir comme une interface.
  • Généralement, le nom des interfaces commence par la lettre 'I'. Par exemple, IComponentEditor ou IDesigner. En respectant cette convention, vous n'avez pas à examiner la déclaration de la classe pour savoir si cette classe se comporte comme une interface.
  • Généralement les interfaces ont un GUID associé. Ce n'est pas obligatoire, mais le code gérant les interfaces s'attend disposer d'un GUID. Vous pouvez utiliser le modificateur __declspec avec l'argument uuid pour associer une interface à un GUID. Pour les interfaces, la macro INTERFACE_UUID fait le même lien.

La déclaration d'interface suivante illustre ces conventions :

 __interface  INTERFACE_UUID("{C527B88F-3F8E-1134-80e0-01A04F57B270}") IHelloWorld : public IInterface
 { 
 public: 
     virtual void __stdcall  SayHelloWorld( void ) = 0 ; 
 };

Généralement, lors de la déclaration d'une classe interface, le code C++Builder déclare aussi une classe DelphiInterface correspondante qui simplifie l'utilisation de l'interface :

 typedef System::DelphiInterface<IHelloWorld> _di_IHelloWorld;

IUnknown et IInterface

Toutes les interfaces Pascal Objet dérivent d'un seul ancêtre commun, IInterface. Les classes interface C++ ne sont pas obligées d'utiliser IInterface comme classe de base, car une classe de style VCL peut l'utiliser comme classe de base supplémentaire, mais le code RTL gérant les interfaces suppose que les méthodes de IInterface sont présentes.

Dans la programmation COM, toutes les interfaces dérivent de IUnknown. La gestion COM dans la RTL est basée sur une définition de IUnknown qui correspond directement à IInterface. Donc, en Pascal Objet, IUnknown et IInterface sont identiques.

Cependant, la définition Pascal Objet de IUnknown ne correspond pas à la définition de IUnknown utilisée dans C++Builder. Le fichier unknwn.h définit IUnknown pour inclure les trois méthodes suivantes :

 virtual  HRESULT STDCALL QueryInterface( const GUID &guid, void **ppv ) = 0;
 virtual  ULONG STDCALL AddRef() = 0;
 virtual  ULONG STDCALL Release() = 0;

Cela correspond à la définition de IUnknown indiquée par Microsoft dans la spécification COM.

A la différence du Pascal Objet, la définition en C++Builder de IInterface n'est pas équivalente à celle de IUnknown. IInterface est un descendant de IUnknown comportant une méthode supplémentaire, Supports :

 template <typename T>
 HRESULT  __stdcall  Supports( DelphiInterface<T>& smartIntf )
 {
     return  QueryInterface( __uuidof (T),
         reinterpret_cast<void **>( static_cast <T**>(&smartIntf)));
 }

Supports vous permet d'obtenir une DelphiInterface pour une autre interface prise en charge à partir de l'objet qui implémente IInterface. Ainsi, par exemple, si vous disposez d'une DelphiInterface pour une interface IMyFirstInterface et si l'objet d'implémentation implémente également IMySecondInterface (dont le type DelphiInterface est _di_IMySecondInterface), vous pouvez obtenir la DelphiInterface pour la deuxième interface comme suit :

 _di_IMySecondInterface MySecondIntf;
 MyFirstIntf->Supports( MySecondIntf );

La RTL utilise un mappage de IUnknown en Pascal Objet. Ce mappage convertit les types utilisés dans les méthodes de IUnknown en types Pascal Objet et renomme les méthodes AddRef et Release en _AddRef et _Release pour indiquer qu'elles ne doivent jamais être appelées directement. En Pascal Objet, le compilateur génère automatiquement les appels nécessaires aux méthodes de IUnknown. Quand les méthodes de IUnknown (ou de IInterface) gérées par les objets RTL sont mappées en C++, cela donne les signatures de méthode suivantes :

 virtual  HRESULT  __stdcall  QueryInterface( const GUID &IID, void *Obj );
 int __stdcall  _AddRef( void );
 int __stdcall  _Release( void );

Cela signifie que les objets RTL qui supportent IUnknown ou IInterface en Pascal Objet ne supportent pas les versions de IUnknown et IInterface qui apparaissent dans C++Builder.

Création de classes gérant IUnknown

De nombreuses classes de la RTL incluent le support des interfaces. En fait, la classe de base de toutes les classes de style Delphi, TObject, dispose d'une méthode GetInterface qui vous permet d'obtenir une classe DelphiInterface pour toute interface supportée par l'instance d'objet. TComponent et TInterfacedObject implémentent les méthodes de IInterface (IUnknown). Donc, tout descendant de TComponent ou TInterfacedObject que vous créez, hérite automatiquement du support des méthodes communes à toutes les interfaces Pascal Objet. En Pascal Objet, quand vous créez un descendant de l'une de ces classes, vous pouvez supporter une nouvelle interface en n'implémentant que les méthodes introduites par la nouvelle interface et en vous remettant à l'implémentation par défaut pour les méthodes de IInterface héritées. Malheureusement, comme les signatures des méthodes de IUnknown et IInterface sont différentes entre C++Builder et les versions utilisées dans les classes RTL, les classes de style Delphi que vous créez n'incluent pas automatiquement le support de IInterface ou IUnknown, même si elles sont dérivées (directement ou pas) de TComponent ou TInterfacedObject. Donc, pour supporter les interfaces que vous définissez en C++ et qui dérivent de IUnknown ou de IInterface, vous devez quand même ajouter une implémentation des méthodes de IUnknown.

Le moyen le plus simple d'implémenter les méthodes de IUnknown dans une classe dérivant de TComponent ou de TInterfacedObject reste quand même d'exploiter le support intégré de IUnknown, en dépit des différences dans les signatures des fonctions. Ajoutez simplement les implémentations des versions C++ des méthodes de IUnknown qui délèguent aux versions héritées de Pascal Objet. Par exemple :

 virtual HRESULT __stdcall QueryInterface(const  GUID &IID, void **Obj)
 {
     return TInterfacedObject::QueryInterface(IID, (void *)Obj); 
  
 
 virtual ULONG __stdcall AddRef() 
 { 
     return TInterfacedObject::_AddRef(); 
 } 
 
 // ...
 virtual ULONG __stdcall Release()
 { 
     return TInterfacedObject::_Release(); 
 }

En ajoutant les implémentations des trois méthodes précédentes à un descendant de TInterfacedObject, votre classe gère complètement IUnknown ou IInterface.

Si vous voulez que les méthodes de IUnknown soient implémentées automatiquement, vous pouvez utiliser la macro INTFOBJECT_IMPL_IUNKNOWN(BASE), définie dans systobj.h :

 class <class name>: public <intf name> {
     INTFOBJECT_IMPL_IUNKNOWN(<base>);
 public:
 // no need to implement QueryInterface, AddRef and Release
 };

Pour éviter l'implémentation des méthodes de IUnknown – à la place de TInterfacedObject, vous pouvez utiliser TCppInterfacedObject, qui implémente déjà QueryInterface, AddRef et Release.

Classes interfaces et gestion de la durée de vie

Les méthodes IUnknown des classes interface ont des conséquences sur la manière dont vous allouez et libérez les objets qui implémentent l'interface. Quand IUnknown est implémentée par un objet COM, l'objet utilise les méthodes de IUnknown pour suivre le nombre de références à son interface en cours d'utilisation. Lorsque ce compteur de références tombe à zéro, l'objet se libère automatiquement. TInterfacedObject implémente les méthodes de IUnknown afin d'effectuer automatiquement le même type de gestion de la durée de vie.

Néanmoins, cette interprétation des méthodes de IUnknown n'est pas strictement nécessaire. Ainsi, l'implémentation par défaut des méthodes de IUnknown fournies par TComponent ne tient pas compte du nombre de références à l'interface, le composant ne se libère donc pas quand ce compteur de références tombe à zéro. En effet; TComponent s'en remet à l'objet spécifié par sa propriété Owner pour le libérer.

Certains composants utilisent un mixte de ces deux modèles. Si leur propriété Owner vaut NULL, ils utilisent le compteur de références sur leur interface pour la gestion de leur durée de vie, et se libèrent eux-mêmes quand ce compteur de références tombe à zéro. S'ils ont un propriétaire, ils ne tiennent pas compte du compteur de références et laissent leur propriétaire les libérer. Attention, que ce soit pour ces objets hybrides ou pour les autres objets utilisant le compteur de références pour la gestion de durée de vie, les objets ne sont pas automatiquement libérés si l'application crée l'objet sans jamais en obtenir d'interface.


Voir aussi