Objects and interfaces

This chapters deals with the similarities and differences between objects and interfaces. The separation between  declaration and implementation is discussed. Idispatch, the main interface in automation is described.

What is an object ?

Let's start with a quick recapitulation of object oriented programming. An object is just some data and some functions (called methods) grouped together.  Objects are described in a class declaration after which the code follows in the implementation section

interface
type
   tMyClass = class    
      private
         MyPrivateVar : string;
      public
         MyVar1 : string;
         MyVar2 : integer;
         procedure MyMethod(MyParam : string); virtual;
end;
implementation
procedure tMyClass.MyMethod(MyParam: string);
begin
   ShowMessage('Just showing ' + MyParam);
end;

The preserved words private and public describe the visibility of the class members.  When an object is created, by using the constructor of the object :
MyObject:= tMyClass.Create;
the user of the object can see MyVar1, MyVar2 and Mymethod.

MyPrivateVar is only visible inside of the object, it could be used in mymethod. This is referred to as encapsulation.

Reusing a class

The good thing about a class is that it can be fully reused. To do this a new class tMySecondClass is declared, this new class is said to inherit from tMyClass, which is said to be the ancestor of tMySecondClass. The method MyMethod was marked as virtual in the the original class. By repeating the method's declaration, this time with the keyword overrride, a new version of the method is announced. The implementation section contains its code. 

interface
type
   tMySecondClass = class(tMyClass)    
      public
         procedure MyMethod(MyParam : string); override;
end;
implementation
procedure tMySecondClass.MyMethod(MyParam: string);
begin
    ShowMessage('Showing more :' + MyParam + DateToStr(date)););
end;

The new version of Mymethod does something else than its ancestor implementation. The two classes are said to be polymorphic.

A Delphi unit consists of two parts. The upper interface part holds all declarations, here is declared what the classes look like. The lower implementation part holds the executable code, here is stated how it will be done. 

Abstract object members

The declaration of Mymethod was declared as virtual, if it had been coded as 

     procedure MyMethod(MyParam : string); virtual; abstract; 

there would have been no implementation of MyMethod in the ancestor class. The declaration states that there is a MyMethod. Everyone who inherits from this class will have to provide an implementation of this method. Which also means that nobody can create an object of the ancestor class as that would lead to object without an implementation of MyMethod. Runtime you will get an "abstract error", in Delphi 6 the compiler itself will checks this.

What is an interface ?

With the abstract keyword we have reached the point where the what and the how get separated. An interface can be described as an object of which only the declaration part (the interface section) is available.  All methods and properties are abstract. 

A type library is filled with interface declarations. 

interface
IFileZapper = interface(IDispatch)
   ['{474E4684-10F7-4E1E-9306-7803B39471DD}']
   function Get_Directory: WideString; safecall;
   procedure Set_Directory(const Value: WideString); safecall;
   function Get_FileMask: WideString; safecall;
   procedure Set_FileMask(const Value: WideString); safecall;
   function Get_FileCount: Integer; safecall;
   function Select(const FileName: WideString): WordBool; safecall;
   procedure DeleteSelected; safecall;
   function Get_AllowDelete: WordBool; safecall;
   procedure Set_AllowDelete(Value: WordBool); safecall;
   property Directory: WideString read Get_Directory write Set_Directory;
   property FileMask: WideString read Get_FileMask write Set_FileMask;
   property FileCount: Integer read Get_FileCount;
   property AllowDelete: WordBool read Get_AllowDelete write Set_AllowDelete;
end;

This was the declaration of the automation server in the first chapter. The typelibrary's unit has a very small implementation part.

implementation
class function CoFileZapper.Create: IFileZapper;
begin
    Result := CreateComObject(CLASS_FileZapper) as IFileZapper;
end;

This is only a class function to obtain an IfileZapper interface. In the chapter on using automation objects we have seen how the client can obtain an interface using this class function. After which it can call methods on the interface if it was just an object variable. On the client side the difference between an interface and an object is only small. 

Implementing an interface

Delphi has a very nice way it handles the implementation of interfaces. In the declaration of a class I can declare which interfaces the class is going to implement

type
TFileZapper = class(TAutoObject, IFileZapper)
   protected
   function Get_Directory: WideString; safecall;
   function Get_FileCount: Integer; safecall;
   function Get_FileMask: WideString; safecall;
   procedure Set_Directory(const Value: WideString); safecall;
   procedure Set_FileMask(const Value: WideString); safecall;
   function Get_AllowDelete: WordBool; safecall;
   function Select(const FileName: WideString): WordBool; safecall;
   procedure DeleteSelected; safecall;
   procedure Set_AllowDelete(Value: WordBool); safecall;
   { Protected declarations }
end;

This declaration states that the class tFileZapper inherits from the (VCL) class tAutoObject and that it implements the interface IfileZapper. (If you like the idea of multiple inheritance you could state that tFileZapper inherits from tAutoObject and from IFileZapper). As IfileZapper has only abstract methods I have to create an implementation for every method and property of IfileZapper. There is nothing I can reuse.

Now the separation of the what (in the typelibrary) and the how (in the tAutoObject descendant) is complete.

A Delphi class can implement more than one interface. As long as the class provides implementations for all the methods, an interface can be added to the declaration. In case of a name conflict, methods can be mapped to methods with a different name. In the VCL source you will find a very nice example of this.

TComObject = class(TObject, IUnknown, ISupportErrorInfo)
   protected
   { IUnknown }
   function IUnknown.QueryInterface = ObjQueryInterface;
   function IUnknown._AddRef = ObjAddRef;
   function IUnknown._Release = ObjRelease;

This class provides an implementation of IsupportErrorInfo and of Iunknown. We will meet the latter in the next paragraph. All methods of Iunknown are mapped on methods of the tComObject class.

Iunknown, the root of COM

Interfaces can inherit from an other interface as well. In the typelibrary you will see 

IFileZapper = interface(IDispatch)

Which means that IfileZapper inherits from Idispatch. And that means no more than that IfileZapper promises to implement all the methods and properties of the Idispatch interface. Which means extra work, again there is nothing that can be reused except the promise. 

Idispatch itself inherits from Iunknown and that is the root interface of COM. Iunknown is responsible for life cycle management and providing clients with an interface to some piece of desired functionality. To do so it has the following methods and properties :

bulletAddRef
bulletRelease
bulletRefCount
bulletQueryInterface

Objects which implement Iunknown, so called interfaced objects, can manage themselves. The object has a refcount property and when this refcount falls back to 0 the object will destroy itself. The refcount is manipulated by the the addref en release methods. Every client using the object, by means of an Iunknown interface, will make calls to these methods. In most programming languages, including Delphi, the compiler will take care of this. In C++ it can be a programmers nightmare.

To do some real work the automation client will need an interface with more methods. In the typelibrary you will find an ID for every interface, which is coded as a Guid. In the declaration it's value immediately follows the name of the interface 

IFileZapper = interface(IDispatch)
   ['{474E4684-10F7-4E1E-9306-7803B39471DD}']

Iunknown 's Queryinterface method will take this interface ID as parameter and hand back (if available) an interface of the desired type. 

Idispatch, the methods

Idispatch is the key interface in all automation objects. The be more precise, every object that implements Idispatch can be automated, provided that it has well behaving implementations of the methods of Idispatch. Let's take a look at these methods:

bulletGetTypeInfoCount
bulletGetTypeInfo
bulletGetIDsOfNames
bulletInvoke 

Two methods deal with the type information of the interface. This is the way to get runtime access to the information in the typelibraray. When there is a typelibrary available, getTypeInfoCount will result in a nonzero number. By using GetTypeInfo the typelibarary itself can be accessed.

In the chapter on using automation servers late binding clients were introduced. These client will come up with the name of a desired method at runtime. GetIdsOfNames is able to map a name to an internal ID. Internally this method will (of course) use the typelibrary to find the info. The Invoke method will use the found ID and will result in a call to the desired method itself.

These four methods form the core of automation as it is done by scripting clients.

Early bound clients (see using automation servers) don't need any typeinfo at runtime. The compiler did all the work and will map all calls directly to the vtable of the automation object.

Idispatch, the Delphi implementation

My automation server tFileZapper now has stated that it will provide an implementation for IfileZapper. For all its methods in the interface I have provided an implementation. But IfileZapper inherites from Idispatch, which means I will have to provide an implementation of the Idispatch methods as well. These are provided by tAutoObject, the VCL class tFileZapper inherits from.

When you walk through the VCL sources you will find :

TAutoObject = class(TTypedComObject, IDispatch)
   protected
   { IDispatch }
   function GetIDsOfNames(...): HResult; virtual; stdcall;
   function GetTypeInfo(...): HResult; virtual; stdcall;
   function GetTypeInfoCount(...): HResult; virtual; stdcall;
   function Invoke(...): HResult; virtual; stdcall;

The same way tFileZapper provided an implementation of IfileZapper, tAutoObject provides an implementation of Idispatch

A lot of interesting things can be seen here. First of all you will see that all the methods of Idispatch are functions which return an HResult. More on this, as well as the stdcall directive in the chapter on error handling

The nicest thing is that the tAutoObject class is a pure Delphi class with a nice inheritance tree

Following the tree you will meet TComobject. We allready have seen that this class provides the implementation of Iunknown.

There is one more thing I would like to mention : all of the Idispatch methods are declared as virtual. Which means you can override them.

Where are we ?

We have seen that the an interface can be described as an object with only abstract members. To the user of an automation object an interface variable programs just as easy as an object variable, provided that there is a way to create the object. 

When it comes to implementing an interface you will need a class whose methods will do the work. Object Pascal has a very clear way to declare and implement interfaces.

What's next ?