Inheritance and Interfaces

From RAD Studio
Jump to: navigation, search

Go Up to C++ and Delphi Class Models


Unlike C++, the Delphi language does not support multiple inheritance. Any classes that you create that have RTL ancestors inherit this restriction. That is, you can not use multiple base classes for a Delphi style C++ class, even if the RTL class is not the immediate ancestor.

Using Interfaces Instead of Multiple Inheritance

For many of the situations where you would use multiple inheritance in C++, Delphi code makes use of interfaces instead. There is no C++ construct that maps directly to the Delphi concept of interface. An Delphi interface acts like a class with no implementation. That is, an interface is like a class where all the methods are pure virtual and there are no data members. While an Delphi class can have only a single parent class, it can support any number of interfaces. Delphi code can assign a class instance to variables of any of those interface types, just as it can assign the class instance to a variable of any ancestor class type. This allows polymorphic behavior for classes that share the same interface, even if they do not have a common ancestor.

In C++Builder, the compiler recognizes classes that have only pure virtual methods and no data members as corresponding to Delphi interfaces. Thus, when you create a Delphi style class, you are permitted to use multiple inheritance, but only if all of the base classes except the one that is a RTL or Delphi style class have no data members and only pure virtual methods.

Note: The interface classes do not need to be Delphi style classes; the only requirement is that they have no data members and only pure virtual methods.

Declaring Interface Classes

You can declare a class that represents an interface just like any other C++ class. However, by using certain conventions, you can make it clearer that the class is intended to act as an interface. These conventions are as follows:

  • Instead of using the class keyword, interfaces are declared using __interface. __interface is a macro that maps to the class keyword. It is not necessary, but makes it clearer that the class is intended to act as an interface.
  • Interfaces typically have names that begin with the letter ‘I’. Examples are IComponentEditor or IDesigner. By following this convention, you do not need to look back at the class declaration to realize when a class is acting as an interface.
  • Interfaces typically have an associated GUID. This is not an absolute requirement, but most of the code that supports interfaces expects to find a GUID. You can use the __declspec modifier with the uuid argument to associate an interface with a GUID. For interfaces, the INTERFACE_UUID macro maps to the same thing.

The following interface declaration illustrates these conventions:

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

Typically, when declaring an interface class, C++Builder code also declares a corresponding DelphiInterface class that makes working with the interface more convenient:

 typedef System::DelphiInterface<IHelloWorld> _di_IHelloWorld;

IUnknown and IInterface

All Delphi interfaces descend from a single common ancestor, IInterface. It is not necessary for C++ interface classes to use IInterface as a base class, in the sense that a Delphi style class can use them as additional base classes, but RTL code that deals with interfaces assume that the IInterface methods are present.

In COM programming, all interfaces descend from IUnknown. COM support in the RTL is based on a definition of IUnknown that maps directly to IInterface. That is, in Delphi, IUnknown and IInterface are identical.

The Delphi definition of IUnknown, however, does not correspond to the definition of IUnknown that is used in C++Builder. The file unknwn.h defines IUnknown to include the following three methods:

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

This corresponds to the definition of IUnknown that is defined by Microsoft as part of the COM specification.

Unlike the case in Delphi, the C++Builder definition of IInterface is not equivalent to its definition of IUnknown. Instead, IInterface is a descendant of IUnknown that includes an additional method, Supports:

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

Supports lets you obtain a DelphiInterface for another supported interface from the object that implements IInterface. Thus, for example, if you have a DelphiInterface for an interface IMyFirstInterface, and the implementing object also implements IMySecondInterface (whose DelphiInterface type is _di_IMySecondInterface), you can obtain the DelphiInterface for the second interface as follows:

 _di_IMySecondInterface MySecondIntf;
 MyFirstIntf->Supports( MySecondIntf );

The RTL uses a mapping of IUnknown into Delphi. This mapping converts the types used in the IUnknown methods to Delphi types, and renames the AddRef and Release methods to _AddRef and _Release in order to indicate that they should never be called directly. (In Delphi, the compiler automatically generates the necessary calls to IUnknown methods.) When the IUnknown (or IInterface) methods that RTL objects support are mapped back into C++, this results in the following method signatures:

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

This means that RTL objects that support IUnknown or IInterface in Delphi do not support the versions of IUnknown and IInterface that appear in C++Builder.

Creating classes that support IUnknown

Many classes in the RTL include interface support. In fact, the base class for all Delphi style classes, TObject, has a GetInterface method that lets you obtain a DelphiInterface class for any interface that an object instance supports. TComponent and TInterfacedObject both implement the IInterface (IUnknown) methods. Thus, any descendant of TComponent or TInterfacedObject that you create automatically inherits support for the common methods of all Delphi interfaces. In Delphi, when you create a descendant of either of these classes, you can support a new interface by implementing only those methods introduced by the new interface, relying on a default implementation for the inherited IInterface methods. Unfortunately, because the signatures of the IUnknown and IInterface methods differ between C++Builder and the versions used in RTL classes, Delphi style classes that you create do not automatically include IInterface or IUnknown support, even if they are derived (directly or indirectly) from TComponent or TInterfacedObject. That is, to support any interfaces you define in C++ that descend from IUnknown or IInterface, you must still add an implementation of the IUnknown methods.

The easiest way to implement the IUnknown methods in a class that descends from TComponent or TInterfacedObject is to take advantage of the built-in IUnknown support, in spite of the differences in function signatures. Simply add implementations for the C++ versions of the IUnknown methods, delegating to the inherited Delphi-based versions. For example:

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

By adding the previous three method implementations to a descendant of TInterfacedObject, your class obtains full IUnknown or IInterface support.

If you want the methods of IUnknown to be implemented automatically you can use the macro INTFOBJECT_IMPL_IUNKNOWN(BASE), defined in systobj.h:

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

To avoid to implement the methods of IUnknown – instead of TInterfacedObject you can use TCppInterfacedObject, which already implements QueryInterface, AddRef, and Release.

Interfaced classes and lifetime management

The IUnknown methods of interface classes have implications for the way you allocate and free the objects that implement the interface. When IUnknown is implemented by a COM object, the object uses the IUnknown methods to keep track of how many references to its interface are in use. When that reference count drops to zero, the object automatically frees itself. TInterfacedObject implements the IUnknown methods to perform this same type of lifetime management.

This interpretation of the IUnknown methods, however, is not strictly necessary. The default implementation of the IUnknown methods provided by TComponent, for example, ignores the reference count on the interface, so that the component does not free itself when its reference count drops to zero. This is because TComponent relies on the object specified by its Owner property to free it.

Some components use a hybrid of these two models. If their Owner property is NULL, they use the reference count on their interface for lifetime management, and free themselves when that reference count drops to zero. If they have an owner, they ignore the reference counting and allow the owner to free them. Note that for such hybrid objects, as well as for any other objects that use reference counting for lifetime management, the objects are not automatically freed if the application creates the object but never obtains an interface from it.


See Also