Vererbung und Interfaces

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu C++- und Object Pascal-Modelle


Im Gegensatz zu C++ unterstützt Object Pascal keine Mehrfachvererbung. Alle Klassen, die Sie erstellen und die RTL-Vorfahren haben, erben diese Einschränkung. Das bedeutet, dass Sie nicht mehrere Basisklassen für eine C++-Klasse im Delphi-Stil verwenden können, auch dann nicht, wenn die RTL-Klasse nicht der direkte Vorfahr ist.

Verwendung von Interfaces anstelle von Mehrfachvererbung

In vielen Situationen, in welchen Sie in C++ die Mehrfachvererbung verwenden würden, setzt Object Pascal-Quelltext stattdessen Interfaces ein. Es gibt kein C++-Konstrukt, das sich direkt dem Object Pascal-Konzept des Interface zuordnen lässt. Ein Object Pascal-Interface verhält sich wie eine Klasse ohne Implementierung. Ein Interface ist also eine Klasse, in der alle Methoden rein virtuell sind, und es keine Daten-Member gibt. Obwohl eine Object Pascal-Klasse nur eine übergeordnete Klasse haben kann, kann sie eine beliebige Anzahl von Interfaces unterstützen. Object Pascal-Quelltext kann eine Klasseninstanz zu Variablen beliebiger Interface-Typen zuweisen, genauso wie die Klasseninstanz einer Variable des Vorfahr-Klassentyps zugewiesen werden kann. Dadurch wird für Klassen, die dasselbe Interface nutzen, polymorphes Verhalten ermöglicht, auch wenn sie keinen gemeinsamen Vorfahren haben.

In C++Builder erkennt der Compiler Klassen, die wie die entsprechenden Object Pascal-Interfaces nur rein virtuelle Methoden und keine Daten-Member enthalten. Daher können Sie, wenn Sie eine Klasse im Delphi-Stil erstellen, Mehrfachvererbung verwenden, aber nur dann, wenn alle Basisklassen - außer der RTL-Klasse oder der Klasse im Delphi-Stil - keine Daten-Member und nur rein virtuelle Methoden enthalten.

Hinweis: Die Interface-Klassen müssen keine Klassen im Delphi-Stil sein. Die einzige Voraussetzung ist, dass sie über keine Daten-Member und nur über rein virtuelle Methoden verfügen.

Deklaration von Interface-Klassen

Eine Klasse, die ein Interface repräsentiert, kann wie jede andere C++-Klasse deklariert werden. Durch die Einhaltung bestimmter Konventionen wird aber deutlicher, dass die Klasse als Interface fungieren soll. Diese Konventionen sind:

  • Anstatt mit dem Schlüsselwort class werden Interfaces mit __interface deklariert. __interface ist ein Makro, das dem Schlüsselwort class entspricht. __interface ist nicht erforderlich, macht es aber klarer, dass die Klasse als Interface fungieren soll.
  • Interface-Namen beginnen typischerweise mit dem Buchstaben ‘I’. Beispiele hierfür sind IComponentEditor oder IDesigner. Wenn Sie sich an diese Konvention halten, müssen Sie nicht in der Klassendeklaration nachsehen, um zu wissen, ob eine Klasse als Interface dient.
  • Interfaces ist typischerweise eine GUID zugeordnet. Das ist keine absolute Voraussetzung, aber Quelltext, der Interfaces unterstützt, erwartet in den meisten Fällen eine GUID. Sie können mit dem Modifizierer __declspec und dem Argument uuid einem Interface eine GUID zuordnen. Bei Interfaces ordnet das Makro INTERFACE_UUID auch eine GUID zu.

Die folgende Interface-Deklaration veranschaulicht diese Konventionen:

 __interface  INTERFACE_UUID("{C527B88F-3F8E-1134-80e0-01A04F57B270}") IHelloWorld :  public  IInterface 
 
 { 

 public : virtual void __stdcall  SayHelloWorld( void ) = 0  ; 
 
 };

Beim Deklarieren einer Interface-Klasse deklariert C++Builder-Quelltext typischerweise auch eine korrespondierende DelphiInterface-Klasse, die die Arbeit mit dem Interface erleichtert:

  typedef System::DelphiInterface<IHelloWorld> _di_IHelloWorld;

IUnknown und IInterface

Alle Object Pascal-Interfaces sind von einem einzigen gemeinsamen Vorfahren, IInterface, abgeleitet. C++-Interface-Klassen müssen IInterface nicht als Basisklasse verwenden, aber RTL-Quelltext für Interfaces geht davon aus, dass die Methoden von IInterface vorhanden sind.

In der COM-Programmierung sind alle Interfaces von IUnknown abgeleitet. Die COM-Unterstützung in der RTL basiert auf einer Definition von IUnknown, die IUnknown direkt zu IInterface zuordnet. Das heißt, in Object Pascal sind IUnknown und IInterface identisch.

Die Object Pascal-Definition von IUnknown entspricht jedoch nicht der Definition von IUnknown, die in C++Builder verwendet wird. Die Datei unknwn.h definiert IUnknown so, dass es die folgenden drei Methoden enthält:

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

Diese Definition entspricht der Definition von IUnknown, die von Microsoft als Bestandteil der COM-Spezifikation definiert wurde.

Im Gegensatz zu Object Pascal entspricht die C++Builder-Definition von IInterface nicht der Definition von IUnknown. IInterface ist in C++Builder von IUnknown abgeleitet, das die zusätzliche Methode, Supports, enthält:

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

Mit Supports ermitteln Sie ein DelphiInterface für ein anderes unterstütztes Interface aus dem Objekt, das IInterface implementiert. Wenn beispielsweise ein DelphiInterface für das Interface IMyFirstInterface vorhanden ist, und das implementierende Objekt auch IMySecondInterface (dessen DelphiInterface-Typ _di_IMySecondInterface ist) implementiert, können Sie das DelphiInterface für das zweite Interface folgendermaßen ermitteln:

 _di_IMySecondInterface MySecondIntf;
 MyFirstIntf->Supports( MySecondIntf );

Die RTL verwendet für Object Pascal ein Mapping von IUnknown. Dieses Mapping konvertiert die in den IUnknown-Methoden verwendeten Typen in Object Pascal-Typen und benennt die Methoden AddRef und Release in _AddRef und _Release um, und gibt damit an, dass sie nie direkt aufgerufen werden sollten. (In Object Pascal erzeugt der Compiler automatisch die für die IUnknown-Methoden erforderlichen Aufrufe.) Wenn die Methoden von IUnknown (oder IInterface), die von RTL-Objekten unterstützt werden, zurück zu C++ gemappt werden, ergeben sich die folgenden Methodensignaturen:

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

Das bedeutet, dass RTL-Objekte, die IUnknown oder IInterface in Object Pascal unterstützen, die Versionen von IUnknown und IInterface in C++Builder nicht unterstützen.

Erstellen von Klassen, die IUnknown unterstützen

Viele Klassen in der RTL enthalten auch eine Interface-Unterstützung. Tatsächlich verfügt die Basisklasse für alle Klassen im Delphi-Stil, TObject, über die Methode GetInterface, mit der Sie eine DelphiInterface-Klasse für alle Interfaces abrufen können, die von einer Objektinstanz unterstützt werden. TComponent und TInterfacedObject implementieren die IInterface-(IUnknown)-Methoden. Daher erben alle Nachkommen von TComponent und TInterfacedObject, die Sie erstellen, automatisch die Unterstützung für die Methoden aller Object Pascal-Interfaces. Wenn Sie in Object Pascal einen Nachkommen einer dieser Klassen erstellen, können Sie ein neues Interface unterstützen, indem Sie nur diejenigen Methoden implementieren, die das neue Interface einführt und eine Standardimplementierung für die geerbten IInterface-Methoden verwenden. Weil die Signaturen der IUnknown- und IInterface-Methoden in C++Builder und den in RTL-Klassen verwendeten Versionen unterschiedlich sind, beinhalten Klassen im Delphi-Stil, die Sie erstellen, leider keine automatische Unterstützung für IInterface und IUnknown, auch dann nicht, wenn sie (direkt oder indirekt) von TComponent oder TInterfacedObject abgeleitet sind. Daher müssen Sie für die Unterstützung von Interfaces, die Sie in C++ erstellen und die von IUnknown oder IInterface abgleitet sind, eine Implementierung der IUnknown-Methoden hinzufügen.

Die einfachste Möglichkeit, die IUnknown-Methoden in einer Klasse zu implementieren, die von TComponent oder TInterfacedObject abgeleitet ist, besteht darin, die integrierte IUnknown-Unterstützung zu nutzen, trotz der unterschiedlichen Funktionssignaturen. Fügen Sie einfach Implementierungen für die C++-Versionen der IUnknown-Methoden hinzu, und delegieren Sie sie an die geerbten Object Pascal-basierten Versionen. Zum Beispiel:

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

Durch Hinzufügen der obigen drei Methodenimplementierungen zu einem Nachkommen von TInterfacedObject erhält Ihre Klasse die vollständige IUnknown- oder IInterface-Unterstützung.

Mit dem in systobj.h definierten Makro INTFOBJECT_IMPL_IUNKNOWN(BASE) können Sie die Methoden von IUnknown automatisch implementieren:

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

Anstelle von TInterfacedObject kann mit TCppInterfacedObject (in der QueryInterface, AddRef und Release bereits implementiert werden) die Implementierung der Methoden von IUnknown vermieden werden.

Interface-Klassen und Verwaltung der Lebensdauer

Die IUnknown-Methoden von Interface-Klassen wirken sich auf die Art und Weise aus, wie Objekte zugewiesen und freigegeben werden, die das Interface implementieren. Wenn IUnknown von einem COM-Objekt implementiert wird, verwaltet das Objekt mit den IUnknown-Methoden die Anzahl der verwendeten Referenzen auf sein Interface. Sobald dieser Referenzzähler den Wert Null erreicht, gibt sich das Objekt automatisch frei. TInterfacedObject implementiert die IUnknown-Methoden zur Durchführung derselben Lebensdauerverwaltung.

Diese Interpretation der IUnknown-Methoden ist jedoch nicht unbedingt erforderlich. Die von TComponent bereitgestellte Standardimplementierung der IUnknown-Methoden ignoriert beispielsweise den Referenzzähler auf dem Interface, so dass die Komponente sich nicht selbst freigibt, wenn der Referenzzähler Null erreicht. TComponent wird nämlich von dem in der Eigenschaft Owner angegebenen Objekt freigegeben.

Einige Komponenten verwenden einen Hybriden dieser beiden Modelle. Wenn ihre Eigenschaft Owner NULL ist, verwenden Sie zur Lebensdauerverwaltung den Referenzzähler auf ihrem Interface und geben sich selbst frei, wenn der Referenzzähler Null erreicht. Wenn ihre Eigenschaft Owner gesetzt ist, ignorieren Sie den Referenzzähler, und der Eigentümer gibt das Objekt frei. Bitte beachten Sie bei solchen Hybridobjekten sowie bei allen anderen Objekten, die zur Lebensdauerverwaltung Referenzzähler verwenden, dass Objekte nicht automatisch freigegeben werden, wenn die Anwendung das Objekt zwar erstellt, aber niemals ein Interface von ihm erhält.


Siehe auch