Interface delegation
This page describes FPC implementation of interface delegation, and its diffrences with Delphi.
Structure of implemented interfaces
RTTI members
Whenever a class is declared as implementing one or more interfaces, its implemented interfaces are stored in table which is accessible at runtime with TObject.GetInterfaceTable
function. This function returns a pointer to a TInterfaceTable
record, which contains number of elements followed by one or more TInterfaceEntry
records:
TInterfaceTable = record EntryCount: ptruint; Entries : array [0..0] of TInterfaceEntry; end;
Each implemented interface is described by TInterfaceEntry
(aligment details omitted, see rtl/inc/objpash.inc for full declaration):
TInterfaceEntry = record IID : pguid; { this is nil for Corba interfaces, non-nil for COM } VTable : Pointer; IOffset : ptruint; { meaning depends on IType field } IIDStr : pshortstring; { never nil. For COM interfaces, this is upper(GuidToString(IID^)) } IType : TInterfaceEntryType; end;
TInterfaceType = ( etStandard, { Implemented directly by the class } { IOffset is offset from start of object instance to hidden field storing interface VMT } etVirtualMethodResult, { Delegated to property of type interface, which is accessed by virtual method } { IOffset is offset from class VMT to slot storing the accessor function } etVirtualMethodClass, { Same, but accessor property has type of class } etStaticMethodResult, { Delegated to property of type interface, which is accessed by static method } { IOffset is direct address of the accessor method } etStaticMethodClass, { Same, but accessor property has type of class } etFieldValue, { Delegated to property of type interface, which is accessed by field } { IOffset is offset from start of object instance to accessor field } etFieldValueClass { Same, but accessor property has type of class } );
Delphi uses different TInterfaceEntry structure. It does not have IIDStr counterpart, because Delphi does not support Corba interfaces. More important, interfaces delegated to class-type properties are stored by Delphi as 'directly implemented', and there's no way for runtime to determine whether they are accessed by field or by method.
Instance changes
For interfaces implemented directly by class, each instance of the class gets a hidden field containing a pointer to VMT of the interface. Thus, every directly implemented interface increases the class instance size by sizeof(Pointer) bytes. An offset to this field is stored by compiler in IOffset
field of the corresponding TInterfaceEntry
. At runtime, the TObject.InitInstance
procedure initializes these hidden fields with proper VMT pointers.
FPC specifics: we use a special value of FPC_EMPTYINTF as a value for class interface table whenever compiler can determine that neither the class itself nor any of its ancestors implement any interfaces. FPC_EMPTYINTF is simply a pointer to interface table with zero entries. Its difference to a nil pointer is that TObject.InitInstance
immediately exits when it encounters FPC_EMPTYINTF. This way, we yield higher constuction speed for the 'normal' (non-interfaced) objects.
Delphi specifics: a hidden field is also added for interfaces delegated to class-type properties.
GetInterface function and the 'as' operator
Using 'as' operator in text of the program typically ends up in a call to TObject.GetInterface
method, unless the class overrides the standard behavior, e.g. by providing a customized QueryInterface method. For Corba interfaces, TObject.GetInterfaceByStr
method is called instead, specifying the string identifier of the interface rather than its GUID.