Implémentation des interfaces : Object Pascal et C++

De Appmethod Topics
Aller à : navigation, rechercher

Remonter à Spécificités du C++ - Index


Cette rubrique décrit les deux méthodes d'implémentation des interfaces dans C++, et compare la méthode Object Pascal d'implémentation des interfaces. Notez que dans C++, vous pouvez utiliser deux stratégies différentes pour l'implémentation des interfaces :

  • Le nouvel attribut __property implements
  • L'héritage


Utilisation de l'attribut __property

Appmethod C++ XE a introduit l'attribut __property implements, qui permet au C++ d'implémenter plus facilement des interfaces d'une façon analogue à l'utilisation de la délégation dans Object Pascal. Pour de plus amples informations, voir Support de l'attribut __property implements dans Appmethod C++ XE.

En commençant par Appmethod C++ XE, il n'est pas nécessaire de réimplémenter et de transférer les méthodes de IUnknown quand vous implémentez les interfaces dans C++. Au lieu de dériver de TInterfacedObject, utilisez le template TCppInterfacedObject : il gère IUnknown, et vous gérez la méthode spécifique à votre interface.

Exemple :

 class TMyPersist: public TCppInterfacedObject<IPersist>
 {
  HRESULT __stdcall GetClassID(CLSID *pClassID)
  {
    *pClassID = CLSID_SOMEVALUE;
    return S_OK;
  }
 };

Comparaison de l'implémentation Object Pascal et C++ des interfaces

Voici deux exemples d'implémentation : Une dans Object Pascal et une dans C++. Les deux exemples exposent la même interface, mais l'exemple Object Pascal est initialisé en différé tandis que celui de C++ ne l'est pas.

Exemple Object Pascal

 unit Unit1;
 
 interface
 
 type
 
  // Interface that exposes an 'Add' method
  IAdder = interface
  ['{D0C74612-9E4D-459A-9304-FACE27E3577D}']
    function Add(I, J: Integer): Integer;
  end;
 
  // Aggregatee that implements IAdder
  TAdder = class(TAggregatedObject, IAdder)
    function Add(I, J: Integer): Integer;
  end;
 
  // Aggregator - implements IAdder via TAdder
  TPasClass = class(TInterfacedObject, IAdder)
    FAdder: TAdder;
    function GetAdder: TAdder;
  public
    destructor Destroy; override;
    property Adder: TAdder read GetAdder write FAdder implements IAdder;
  end;
 
 function TestAdd(I, J: Integer): Integer; 
 
 implementation
 
 { TAdder }
 function TAdder.Add(I, J: Integer): Integer;
 begin
   Result := I+J;
 end;
 
 { TPasClass }
 destructor TPasClass.Destroy;
 begin
   FAdder.Free;
   inherited;
 end;
 
 function TPasClass.GetAdder: TAdder;
 begin
   if FAdder = nil then
     FAdder := TAdder.Create(Self as IInterface);
   Result := FAdder;
 end;
 
 // Adds using TClass' IAdder
 function TestAdd(I, J: Integer): Integer; 
 var
   punk: IInterface;
 begin
   punk := TPasClass.Create as IInterface;
   Result := (punk as IAdder).Add(I, J);
 end;
 
 end.

Exemple C++
L'exemple C++ n'est pas initialisé en différé :

 #include <stdio.h>
 
 // Interface that exposes an Add(..) method
 __interface  INTERFACE_UUID("{D0C74612-9E4D-459A-9304-FACE27E3577D}") IAdder  : public    System::IInterface 
 {
    virtual int __fastcall Add(int I, int J) = 0 ;
 };
 typedef System::DelphiInterface<IAdder> _di_IAdder;
 
 
 // Aggregatee that implements IAdder
 class TCppAdder: public TCppAggregatedObject<IAdder> {
 public:
    __fastcall TCppAdder(const _di_IInterface Controller) : _AGGREGATED_CLASS(Controller)
    {}
      
    int __fastcall Add(int i, int j) 
    {
        return i+j;
    }
 };
 
 
 // Aggregator - exposes IAdder via TCppAdder
 class TCppClass : public TInterfacedObject {
 private:
    TCppAdder* obj; 
    IAdder* FAdder;
 protected:
    void initAggregatee() 
    {
        _di_IInterface punk;
        GetInterface(punk);
        _di_IAdder adder;
        (obj = new TCppAdder(punk))->GetInterface(adder);
        FAdder = adder;
    }
 public:
    __fastcall TCppClass()
    {
        initAggregatee();
    }
    __fastcall ~TCppClass()
    {
        delete obj;
    }
    __property IAdder* Adder = { read=FAdder, implements };
 };

 // Test: Invoke IAdder.Add(..) using TCppClass
 int main()
 {
    TCppClass* ptr; 
    _di_IAdder ia;
    if (!((ptr = new TCppClass())->GetInterface(ia)))
    {
        delete ptr;
    }
    else
    {
        int result = ia->Add(10, 20);
        printf("Add Result = %d\n", result);
    }
    return 0;
 }

Utilisation de l'héritage

L'exemple suivant compare l'implémentation d'une interface (en utilisant l'héritage) dans Object Pascal et C++.

Voici le code Object Pascal pour écrire une classe qui implémente l'interface IPersist afin de ne définir qu'une méthode unique, GetClassID :

Object Pascal

Interface

 IPersist = interface(IUnknown)
    ['0000010C-0000-0000-C000-000000000046']
    function GetClassID(out classID: TCLSID): HResult; stdcall;
 end;

Classe

 TMyPersist = class(TInterfacedObject, IPersist)
    function GetClassID(out classID: TCLSID): HResult; stdcall;
 end;
 
 function TMyPersist.GetClassID(out classID: TCLSID): HResult;
 begin
   classID := CLSID_SOMEVALUE;
   Result := S_OK;
 end;

Par comparaison, voici le code C++ pour écrire la même classe avec utilisation de l'héritage :

C++

Interface

 struct __declspec(uuid("0000010c-0000-0000-C000-000000000046")) 
 IPersist : public IUnknown
 {
  public:
     virtual HRESULT __stdcall GetClassID(CLSID *pClassID) = 0;        
 };

Classe

 class TMyPersist: public TInterfacedObject, IPersist
 {
  HRESULT __stdcall GetClassID(CLSID *pClassID)
  {
    *pClassID = CLSID_SOMEVALUE;
    return S_OK;
  }
 
  // Methods of IUnknown have to be reimplemented and forwarded :(
  HRESULT __stdcall QueryInterface(const GUID& IID, void **Obj)
 		            { return GetInterface(IID, Obj) ?
 		                      S_OK : E_NOINTERFACE; }
  ULONG __stdcall AddRef()  { return TInterfacedObject::_AddRef();  }
  ULONG __stdcall Release() { return TInterfacedObject::_Release(); }
 };

Un contrôle ActiveX typique implémente au moins 21 interfaces. Ainsi, l'implémentation des interfaces dans Appmethod C++ est plutôt fastidieuse, à moins que vous n'utilisiez l'attribut __property implements.

Le framework Object Pascal ActiveX (DAX) fournit des classes d'assistance (telles que TConnectionPoints et TPropertyPageImpl) dont les classes parent (dans ce cas, TActiveXControl et TActiveXPropertyPage) ne sont pas directement dérivées des interfaces implémentées par les assistants. Object Pascal utilise la directive implements dans les déclarations de propriétés qui sont mappées sur les assistants. C'est la directive implements qui fait que le compilateur Object Pascal enregistre l'interface dans la table RTTI InterfaceTable de la classe.

De même, l'attribut __property implements fait que le compilateur C++ enregistre l'interface dans la table RTTI InterfaceTable de la classe. Cette table définit comment IUnknown.QueryInterface est implémentée, et par conséquent elle indique au monde extérieur qu'une classe particulière implémente une interface.

Dans l'exemple C++ TMyPersist ci-dessus, vous pouvez voir que QueryInterface appelle simplement TObject.GetInterface:

 // Methods of IUnknown have to be reimplemented and forwarded :(
  HRESULT __stdcall QueryInterface(const GUID& IID, void **Obj)
 			    { return GetInterface(IID, Obj) ?
 			                      S_OK : E_NOINTERFACE; }

TObject.GetInterface est poussée de la table InterfaceTable dans une métadonnée de classe. S'il n'existe pas d'entrée InterfaceTable pour une interface, la classe doit alors écrire une implémentation brute de QueryInterface pour gérer chaque interface qu'elle implémente plutôt que de laisser la logique existante du TObject principal gérer cette tâche. C'est ainsi la raison pour laquelle les développeurs COM utilisent typiquement des frameworks tels que ATL, MFC, BaseCtl et DAX au lieu d'écrire des implémentations IUnknown brutes.

C++ ne serait pas capable de profiter des avantages des assistants ActiveX fournis par DAX sans un support de __property implements.

Voir aussi