Chapter 10
Object interfaces

An object interface--or simply interface--defines methods that can be implemented by a class. Interfaces are declared like classes, but cannot be directly instantiated and do not have their own method definitions. Rather, it is the responsibility of any class that supports an interface to provide implementations for the interface's methods. A variable of an interface type can reference an object whose class implements that interface; however, only methods declared in the interface can be called using such a variable.

Interfaces offer some of the advantages of multiple inheritance without the semantic difficulties. They are also essential for using distributed object models, including COM (the Component Object Model) and CORBA (Common Object Request Broker Architecture). Objects built with Delphi that support interfaces can interact with COM objects written in C++, Java, and other languages.

Interface types

Interfaces, like classes, can be declared only in the outermost scope of a program or unit, not in a procedure or function declaration. An interface type declaration has the form

type interfaceName = interface (ancestorInterface)
  ['{GUID}']
  memberList
end;

where (ancestorInterface) and ['{GUID}'] are optional. In most respects, interface declarations resemble class declarations, but the following restrictions apply.

Here is an example of an interface declaration:

type
  IMalloc = interface(IUnknown)
    ['{00000002-0000-0000-C000-000000000046}']
    function Alloc(Size: Integer): Pointer; stdcall;
    function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;
    procedure Free(P: Pointer); stdcall;
    function GetSize(P: Pointer): Integer; stdcall;
    function DidAlloc(P: Pointer): Integer; stdcall;
    procedure HeapMinimize; stdcall;
  end;

IUnknown and inheritance

An interface, like a class, inherits all of its ancestors' methods. But interfaces, unlike classes, do not implement methods. What an interface inherits is the obligation to implement methods--an obligation that devolves onto any class supporting the interface.

The declaration of an interface can specify an ancestor interface. If no ancestor is specified, the interface is a direct descendant of IUnknown, which is defined in the System unit and is the ultimate ancestor of all other interfaces. IUnknown declares three methods: QueryInterface, _AddRef, and _Release. QueryInterface provides the means to move freely among the different interfaces that an object supports. _AddRef and _Release provide lifetime management for interface references. The easiest way to implement these methods is to derive the implementing class from the System unit's TInterfacedObject.

Interface identification

An interface declaration can specify a globally unique identifier (GUID), represented by a string literal enclosed in brackets immediately preceding the member list. The GUID part of the declaration must have the form

['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

where each x is a hexadecimal digit (0 through 9 or A through F).

A GUID is a 16-byte binary value that uniquely identifies an interface. If an interface has a GUID, you can use interface querying to get references to its implementations. (See "Interface querying".)

The TGUID and PGUID types, declared in the System unit, are used to manipulate GUIDs.

type
  PGUID = ^TGUID;
  TGUID = packed record
    D1: Longword;
    D2: Word;
    D3: Word;
    D4: array[0..7] of Byte;
  end;

When you declare a typed constant of type TGUID, you can use a string literal to specify its value. For example,

const IID_IMalloc: TGUID = '{00000002-0000-0000-C000-000000000046}';

In procedure and function calls, either a GUID or an interface identifier can serve as a value or constant parameter of type TGUID. For example, given the declaration

function Supports(Unknown: IUnknown; const IID: TGUID): Boolean;

Supports can be called in either of two ways:

if Supports(Allocator, IMalloc) then ...
if Supports(Allocator, IID_IMalloc) then ...

Calling conventions

The default calling convention is register, but interfaces shared among modules (especially if they are written in different languages) should declare all methods with stdcall. Use safecall to implement methods of dual interfaces (as described in "Dual interfaces") and CORBA interfaces.

For more information about calling conventions, see "Calling conventions".

Interface properties

Properties declared in an interface are accessible only through expressions of the interface type; they cannot be accessed through class-type variables. Moreover, interface properties are visible only within programs where the interface is compiled. COM objects do not have properties.

In an interface, property read and write specifiers must be methods, since fields are not available.

Forward declarations

An interface declaration that ends with the reserved word interface and a semicolon, without specifying an ancestor, GUID, or member list, is a forward declaration. A forward declaration must be resolved by a defining declaration of the same interface within the same type declaration section. In other words, between a forward declaration and its defining declaration, nothing can occur except other type declarations.

Forward declarations allow mutually dependent interfaces. For example,

type
  IControl = interface;
  IWindow = interface
    ['{00000115-0000-0000-C000-000000000044}']
    function GetControl(Index: Integer): IControl;
    ...
  end;
  IControl = interface
    ['{00000115-0000-0000-C000-000000000049}']
    function GetWindow: IWindow;
    ...
  end;

Mutually derived interfaces are not allowed. For example, it is not legal to derive IWindow from IControl and also derive IControl from IWindow.

Implementing interfaces

Once an interface has been declared, it must be implemented in a class before it can be used. The interfaces implemented by a class are specified in the class's declaration, after the name of the class's ancestor. Such declarations have the form

type className = class (ancestorClass, interface1, ..., interfacen)
  memberList
 end;

For example,

type
  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
  ...
  end;

declares a class called TMemoryManager that implements the IMalloc and IErrorInfo interfaces. When a class implements an interface, it must implement (or inherit an implementation of) each method declared in the interface.

Here is the declaration of TInterfacedObject in the System unit.

type
  TInterfacedObject = class(TObject, IUnknown)
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    property RefCount: Integer read FRefCount;
  end;

TInterfacedObject implements the IUnknown interface. Hence TInterfacedObject declares and implements each of IUnknown's three methods.

Classes that implement interfaces can also be used as base classes. (The first example above declares TMemoryManager as a direct descendent of TInterfacedObject.) Since every interface inherits from IUnknown, a class that implements interfaces must implement the QueryInterface, _AddRef, and _Release methods. The System unit's TInterfacedObject implements these methods and is thus a convenient base from which to derive other classes that implement interfaces.

When an interface is implemented, each of its methods is mapped onto a method in the implementing class that has the same result type, the same calling convention, the same number of parameters, and identically typed parameters in each position. By default, each interface method is mapped to a method of the same name in the implementing class.

Method resolution clauses

You can override the default name-based mappings by including method resolution clauses in a class declaration. When a class implements two or more interfaces that have identically named methods, use method resolution clauses to resolve the naming conflicts.

A method resolution clause has the form

procedure interface.interfaceMethod = implementingMethod;

or

function interface.interfaceMethod = implementingMethod;

where implementingMethod is a method declared in the class or one of its ancestors. The implementingMethod can be a method declared later in the class declaration, but cannot be a private method of an ancestor class declared in another module.

For example, the class declaration

type
  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
    function IMalloc.Alloc = Allocate;
    procedure IMalloc.Free = Deallocate;
    ...
  end;

maps IMalloc's Alloc and Free methods onto TMemoryManager's Allocate and Deallocate methods.

A method resolution clause cannot alter a mapping introduced by an ancestor class.

Changing inherited implementations

Descendant classes can change the way a specific interface method is implemented by overriding the implementing method. This requires that the implementing method be virtual or dynamic.

A class can also reimplement an entire interface that it inherits from an ancestor class. This involves relisting the interface in the descendant class's declaration. For example,

type
  IWindow = interface
    ['{00000115-0000-0000-C000-000000000146}']
    procedure Draw;
    ...
  end;

  TWindow = class(TInterfacedObject, IWindow)  // TWindow implements IWindow
    procedure Draw;
    ...
  end;

  TFrameWindow = class(TWindow, IWindow)  // TFrameWindow reimplements IWindow
    procedure Draw;
    ...
  end;

Reimplementing an interface hides the inherited implementation of the same interface. Hence method resolution clauses in an ancestor class have no effect on the reimplemented interface.

Implementing interfaces by delegation

The implements directive allows you to delegate implementation of an interface to a property in the implementing class. For example,

property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

declares a property called MyInterface that implements the interface IMyInterface.

The implements directive must be the last specifier in the property declaration and can list more than one interface, separated by commas. The delegate property

Delegating to an interface-type property

If the delegate property is of an interface type, that interface, or an interface from which it derives, must occur in the ancestor list of the class where the property is declared. The delegate property must return an object whose class completely implements the interface specified by the implements directive, and which does so without method resolution clauses. For example,

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;

  TMyClass = class(TObject, IMyInterface)
    FMyInterface: IMyInterface;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;

var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyInterface := ...  // some object whose class implements IMyInterface
  MyInterface := MyClass;
  MyInterface.P1;
end;

Delegating to a class-type property

If the delegate property is of a class type, that class and its ancestors are searched for methods implementing the specified interface before the enclosing class and its ancestors are searched. Thus it is possible to implement some methods in the class specified by the property, and others in the class where the property is declared. Method resolution clauses can be used in the usual way to resolve ambiguities or specify a particular method. An interface cannot be implemented by more than one class-type property. For example,

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyImplClass = class
    procedure P1;
    procedure P2;
  end;
  TMyClass = class(TInterfacedObject, IMyInterface)
    FMyImplClass: TMyImplClass;
    property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
    procedure IMyInterface.P1 = MyP1;
    procedure MyP1;
  end;
procedure TMyImplClass.P1;
...
procedure TMyImplClass.P2;
...
procedure TMyClass.MyP1;
...
var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyImplClass := TMyImplClass.Create;
  MyInterface := MyClass;
  MyInterface.P1;    // calls TMyClass.MyP1;
  MyInterface.P2;    // calls TImplClass.P2;
end;

Interface references

If you declare a variable of an interface type, the variable can reference instances of any class that implements the interface. Such variables allow you to call interface methods without knowing at compile time where the interface is implemented. But they are subject to the following limitations.

For example,

type
  IAncestor = interface
  end;

  IDescendant = interface(IAncestor)
    procedure P1;
  end;

  TSomething = class(TInterfacedObject, IDescendant)
    procedure P1;
    procedure P2;
  end;
  ...
var
  D: IDescendant;
  A: IAncestor;
begin
  D := TSomething.Create;  // works!
  A := TSomething.Create;  // error
  D.P1;  // works!
  D.P2;  // error
end;

In this example,

Interface references are managed through reference-counting, which depends on the _AddRef and _Release methods inherited from IUnknown. When an object is referenced only through interfaces, there is no need to destroy it manually; the object is automatically destroyed when the last reference to it goes out of scope.

Global interface-type variables can be initialized only to nil.

To determine whether an interface-type expression references an object, pass it to the standard function Assigned.

Interface assignment-compatibility

A class type is assignment-compatible with any interface type implemented by the class. An interface type is assignment-compatible with any ancestor interface type. The value nil can be assigned to any interface-type variable.

An interface-type expression can be assigned to a variant. If the interface is of type IDispatch or a descendant, the variant receives the type code varDispatch. Otherwise, the variant receives the type code varUnknown.

A variant whose type code is varEmpty, varUnknown, or varDispatch can be assigned to an IUnknown variable. A variant whose type code is varEmpty or varDispatch can be assigned to an IDispatch variable.

Interface typecasts

Interface types follow the same rules as class types in variable and value typecasts. Class-type expressions can be cast to interface types--for example, IMyInterface(SomeObject)--provided the class implements the interface.

An interface-type expression can be cast to Variant. If the interface is of type IDispatch or a descendant, the resulting variant has the type code varDispatch. Otherwise, the resulting variant has the type code varUnknown.

A variant whose type code is varEmpty, varUnknown, or varDispatch can be cast to IUnknown. A variant whose type code is varEmpty or varDispatch can be cast to IDispatch.

Interface querying

You can use the as operator to perform checked interface typecasts. This is known as interface querying, and it yields an interface-type expression from an object reference or from another interface reference, based on the actual (runtime) type of the object. An interface query has the form

object as interface

where object is an expression of an interface or variant type or denotes an instance of a class that implements an interface, and interface is any interface declared with a GUID.

An interface query returns nil if object is nil. Otherwise, it passes the GUID of interface to the QueryInterface method in object, raising an exception unless QueryInterface returns zero. If QueryInterface returns zero (indicating that object's class implements interface), the interface query returns an interface reference to object.

Automation objects

An object whose class implements the IDispatch interface (declared in the System unit) is an Automation object.

Dispatch interface types

Dispatch interface types define the methods and properties that an Automation object implements through IDispatch. Calls to methods of a dispatch interface are routed through IDispatch's Invoke method at runtime; a class cannot implement a dispatch interface.

A dispatch interface type declaration has the form

type interfaceName = dispinterface
  ['{GUID}']
  memberList
end;

where ['{GUID}'] is optional and memberList consists of property and method declarations. Dispatch interface declarations are similar to regular interface declarations, but they cannot specify an ancestor. For example,

type
  IStringsDisp = dispinterface
    ['{EE05DFE2-5549-11D0-9EA9-0020AF3D82DA}']
    property ControlDefault[Index: Integer]: OleVariant dispid 0; default;
    function Count: Integer; dispid 1;
    property Item[Index: Integer]: OleVariant dispid 2;
    procedure Remove(Index: Integer); dispid 3;
    procedure Clear; dispid 4;
    function Add(Item: OleVariant): Integer; dispid 5;
    function _NewEnum: IUnknown; dispid -4;
  end;

Dispatch interface methods

Methods of a dispatch interface are prototypes for calls to the Invoke method of the underlying IDispatch implementation. To specify an Automation dispatch ID for a method, include the dispid directive in its declaration, followed by an integer constant; specifying an already used ID causes an error.

A method declared in a dispatch interface cannot contain directives other than dispid. Parameter and result types must be automatable--that is, they must be Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool, or any interface type.

Dispatch interface properties

Properties of a dispatch interface do not include access specifiers. They can be declared as readonly or writeonly. To specify a dispatch ID for a property, include the dispid directive in its declaration, followed by an integer constant; specifying an already used ID causes an error. Array properties can be declared as default. No other directives are allowed in dispatch-interface property declarations.

Accessing Automation objects

Use variants to access Automation objects. When a variant references an Automation object, you can call the object's methods and read or write to its properties through the variant. To do this, you must include ComObj in the uses clause of one of your units or your program or library.

Automation object method calls are bound at runtime and require no previous method declarations. The validity of these calls is not checked at compile time.

The following example illustrates Automation method calls. The CreateOleObject function (defined in ComObj) returns an IDispatch reference to an Automation object and is assignment-compatible with the variant Word.

var
  Word: Variant;
begin
  Word := CreateOleObject('Word.Basic');
  Word.FileNew('Normal');
  Word.Insert('This is the first line'#13);
  Word.Insert('This is the second line'#13);
  Word.FileSaveAs('c:\temp\test.txt', 3);
end;

You can pass interface-type parameters to Automation methods.

Variant arrays with an element type of varByte are the preferred method of passing binary data between Automation controllers and servers. Such arrays are subject to no translation of their data, and can be efficiently accessed using the VarArrayLock and VarArrayUnlock routines.

Automation object method-call syntax

The syntax of an Automation object method call or property access is similar to that of a normal method call or property access. Automation method calls, however, can use both positional and named parameters. (But some Automation servers do not support named parameters.)

A positional parameter is simply an expression. A named parameter consists of a parameter identifier, followed by the := symbol, followed by an expression. Positional parameters must precede any named parameters in a method call. Named parameters can be specified in any order.

Some Automation servers allow you to omit parameters from a method call, accepting their default values. For example,

Word.FileSaveAs('test.doc');
Word.FileSaveAs('test.doc', 6);
Word.FileSaveAs('test.doc',,,'secret');
Word.FileSaveAs('test.doc', Password := 'secret');
Word.FileSaveAs(Password := 'secret', Name := 'test.doc');

Automation method call parameters can be of integer, real, string, Boolean, and variant types. A parameter is passed by reference if the parameter expression consists only of a variable reference, and if the variable reference is of type Byte, Smallint, Integer, Single, Double, Currency, TDateTime, AnsiString, WordBool, or Variant. If the expression is not of one of these types, or if it is not just a variable, the parameter is passed by value. Passing a parameter by reference to a method that expects a value parameter causes COM to fetch the value from the reference parameter. Passing a parameter by value to a method that expects a reference parameter causes an error.

Dual interfaces

A dual interface is an interface that supports both compile-time binding and runtime binding through Automation. Dual interfaces must descend from IDispatch.

All methods of a dual interface (except from those inherited from IUnknown and IDispatch) must use the safecall convention, and all method parameter and result types must be automatable. (The automatable types are Byte, Currency, Real, Double, Real48, Integer, Single, Smallint, AnsiString, ShortString, TDateTime, Variant, OleVariant, and WordBool.)



Otros Links de Interés:

Cocina  -  Videos  -  Juegos Gratis  - Postales cachondas  - Cine  - Programas Gratis  -  Letras de Canciones

Listas de todos los Tutoriales Gratis. 1998- 2007 - -

Los tutoriales y cursos aquí reunidos son una recopilación de los mejores encontrados en Internet.
El crédito y copyright de los mismos si lo hubiere corresponde al autor de cada uno de ellos.
Si tu tutorial o curso está aquí, y deseas darlo de baja de esta recopilación o quieres añadir el tuyo, envíanos un mensaje desde
aquí

Publispain - Fun