Implémentation des interfaces : Delphi et C++
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 Delphi 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
Sommaire
Utilisation de l'attribut __property
L'attribut __property implements
permet au C++ d'implémenter plus facilement des interfaces d'une façon analogue à l'utilisation de la délégation dans Delphi. Pour de plus amples informations, voir Prise en charge de l'attribut __property implements dans C++Builder 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 Delphi et C++ des interfaces
Voici deux exemples d'implémentation : Une dans Delphi et une dans C++. Les deux exemples exposent la même interface, mais l'exemple Delphi est initialisé en différé tandis que celui de C++ ne l'est pas.
Exemple Delphi
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 Delphi et C++.
Voici le code Delphi pour écrire une classe qui implémente l'interface IPersist afin de ne définir qu'une méthode unique, GetClassID :
Delphi | |
---|---|
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(); }
};
|
Assistances ActiveX
Un contrôle ActiveX typique implémente au moins 21 interfaces. Ainsi, l'implémentation des interfaces dans C++Builder est plutôt fastidieuse, à moins que vous n'utilisiez l'attribut __property implements
.
Le framework Delphi 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. Delphi 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 Delphi 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 une prise en charge de __property implements
.