Interface References (Delphi)

From RAD Studio
(Redirected from Interface References)
Jump to: navigation, search

Go Up to Object Interfaces Index

If you declare a variable of an interface type, the variable can reference instances of any class that implements the interface. These topics describe Interface references and indicate related topics.

Implementing Interface References

Interface reference variables allow you to call interface methods without knowing, at compile time, where the interface is implemented. But they are subject to the following:

  • An interface-type expression gives you access only to methods and properties declared in the interface, not to other members of the implementing class.
  • An interface-type expression cannot reference an object whose class implements a descendent interface, unless the class (or one that it inherits from) explicitly implements the ancestor interface as well.

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, A is declared as a variable of type IAncestor. Because TSomething does not list IAncestor among the interfaces it implements, a TSomething instance cannot be assigned to A. But if you changed TSomething's declaration to:

 TSomething = class(TInterfacedObject, IAncestor, IDescendant)
  // ...

the first error would become a valid assignment. D is declared as a variable of type IDescendant. While D references an instance of TSomething, you cannot use it to access TSomething's P2 method, since P2 is not a method of IDescendant. But if you changed D's declaration to:

 D: TSomething;

the second error would become a valid method call.

On the Win32 platform, interface references are typically managed through reference-counting, which depends on the _AddRef and _Release methods inherited from System.IInterface. Using the default implementation of reference counting, 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. Some classes implement interfaces to bypass this default lifetime management, and some hybrid objects use reference counting only when the object does not have an owner.

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

Variables of a given class type are assignment-compatible with any interface type implemented by the class. Variables of an interface type are 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 IInterface variable. A variant whose type code is varEmpty or varDispatch can be assigned to an IDispatch variable.

Interface Typecasts

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 IInterface. 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 (run-time) type of 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 the interface to the QueryInterface method in object, raising an exception unless QueryInterface returns zero. If QueryInterface returns zero (indicating that the object's class implements the interface), the interface query returns an interface reference to object.

Casting Interface References to Objects

The as operator can also be used to cast an interface reference back to the object from which it was obtained. This casting only works for interfaces obtained from Delphi objects. For example:

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   { Create an interfaced object and extract an interface from it. }
   LIntfRef := TInterfacedObject.Create();
 
   { Cast the interface back to the original object. }
   LObj := LIntfRef as TInterfacedObject;
 end;

The above example shows how to obtain the original object from which the interface reference was obtained. This technique is useful when possessing an interface reference is simply not enough.

The as operator raises an exception if the interface was not extracted from the given class:

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   { Create an interfaced object and extract an interface from it. }
   LIntfRef := TInterfacedObject.Create();
 
   try
     { Cast the interface to a TComponent. }
     LObj := LIntfRef as TComponent;
   except
     Writeln('LIntfRef was not referencing a TComponent instance');
   end;  
 end;

You can also perform normal type casting (unsafe) from an interface reference to an object. Like in the case of object unsafe casting, this method does not raise any exceptions. The difference between the unsafe object-to-object casting and unsafe interface-to-object casting is that while the first returns a valid pointer in case of incompatible types, the later returns nil. The example describes the use of unsafe casting:

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   { Create an interfaced object and extract an interface from it. }
   LIntfRef := TInterfacedObject.Create();
 
   { Cast the interface to a TComponent. }
   LObj := TComponent(LIntfRef);
 
   if LObj = nil then
     Writeln('LIntfRef was not referencing a TComponent instance');
 
   { Cast the interface to a TObject. }
   LObj := TObject(LIntfRef);
 
   if LObj <> nil then
     Writeln('LIntfRef was referencing a TObject (or descendant).');
 end;

To avoid potential nil references, use the is operator to verify whether the interface reference was extracted from a given class:

 if Intf is TCustomObject then ...

Note: Make sure you are using Delphi-only objects when using the unsafe casting or the as and is operators.

See Also