Difference between revisions of "Interface delegation"

From Lazarus wiki
Jump to navigationJump to search
m
 
(7 intermediate revisions by 5 users not shown)
Line 1: Line 1:
This page describes FPC implementation of interface delegation, and its diffrences with Delphi.
+
{{Interface_delegation}}
 +
 
 +
back to contents [[FPC internals]]
 +
 
 +
This page describes FPC implementation of interface delegation, and its differences with Delphi.
  
 
== Structure of implemented interfaces ==
 
== Structure of implemented interfaces ==
Line 5: Line 9:
 
=== RTTI members ===
 
=== 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 <code>TObject.GetInterfaceTable</code> function. This function returns a pointer to a <code>TInterfaceTable</code> record, which contains number of elements followed by one or more <code>TInterfaceEntry</code> records:
+
Whenever a class is declared as implementing one or more interfaces, its implemented interfaces are stored in a table which is accessible at runtime with the <code>TObject.GetInterfaceTable</code> function. This function returns a pointer to a <code>TInterfaceTable</code> record, which contains a number of elements followed by one or more <code>TInterfaceEntry</code> records:
  
  TInterfaceTable = record
+
<syntaxhighlight lang=pascal>
    EntryCount: ptruint;
+
TInterfaceTable = record
    Entries  : array [0..0] of TInterfaceEntry;
+
  EntryCount: ptruint;
  end;
+
  Entries  : array [0..0] of TInterfaceEntry;
 +
end;
 +
</syntaxhighlight>
  
Each implemented interface is described by <code>TInterfaceEntry</code> (aligment details omitted, see rtl/inc/objpash.inc for full declaration):
+
Each implemented interface is described by a <code>TInterfaceEntry</code> (alignment details omitted, see rtl/inc/objpash.inc for full declaration):
  
  TInterfaceEntry = record
+
<syntaxhighlight lang=pascal>
    IID    : pguid;        { this is nil for Corba interfaces, non-nil for COM }
+
TInterfaceEntry = record
    VTable  : Pointer;
+
  IID    : pguid;        { this is nil for Corba interfaces, non-nil for COM }
    IOffset : ptruint;      { meaning depends on IType field, see below }
+
  VTable  : Pointer;
    IIDStr  : pshortstring; { never nil. For COM interfaces, this is upper(GuidToString(IID^)) }
+
  IOffset : ptruint;      { meaning depends on IType field, see below }
    IType  : TInterfaceEntryType;
+
  IIDStr  : pshortstring; { never nil. For COM interfaces, this is upper(GuidToString(IID^)) }
  end;
+
  IType  : TInterfaceEntryType;
 +
end;
  
  TInterfaceEntryType = (
+
InterfaceEntryType = (
    etStandard,            { Implemented directly by the class }
+
  etStandard,            { Implemented directly by the class }
                            {  IOffset is offset from start of object instance to hidden field storing interface VMT }
+
                          {  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 }
+
  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 }
+
                          {  IOffset is offset from class VMT to slot storing the accessor function }
    etStaticMethodResult,  { Delegated to property of type interface, which is accessed by static method }
+
  etStaticMethodResult,  { Delegated to property of type interface, which is accessed by static method }
                            {  IOffset is direct address of the accessor method }
+
                          {  IOffset is direct address of the accessor method }
    etFieldValue,          { Delegated to property of type interface, which is accessed by field }
+
  etFieldValue,          { Delegated to property of type interface, which is accessed by field }
                            {  IOffset is offset from start of object instance to accessor field }
+
                          {  IOffset is offset from start of object instance to accessor field }
    etVirtualMethodClass,  { Same as etVirtualMethodResult, but accessor property has class type }
+
  etVirtualMethodClass,  { Same as etVirtualMethodResult, but accessor property has class type }
    etStaticMethodClass,    { Same as etStaticMethodResult, but accessor property has class type }
+
  etStaticMethodClass,    { Same as etStaticMethodResult, but accessor property has class type }
    etFieldValueClass      { Same as etFieldValue, but accessor property has class type }
+
  etFieldValueClass      { Same as etFieldValue, but accessor property has class type }
  );
+
);
 +
</syntaxhighlight>
  
Delphi uses different TInterfaceEntry structure. Since Delphi does not support Corba interfaces, it does not have IIDStr counterpart. More important, interfaces delegated to class-type properties are stored by Delphi as 'directly implemented', and there's no easy way for runtime to determine whether they are accessed by field or by method.
+
Delphi uses a different TInterfaceEntry structure. Since Delphi does not support Corba interfaces, it does not have an IIDStr counterpart. More importantly, interfaces delegated to class-type properties are stored by Delphi as 'directly implemented', and there's no easy way for runtime to determine whether they are accessed by field or by method.
  
 
=== Instance changes ===
 
=== 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 <code>IOffset</code> field of the corresponding <code>TInterfaceEntry</code>. At runtime, the <code>TObject.InitInstance</code> procedure initializes these hidden fields with proper VMT pointers.
+
For interfaces implemented directly by a class, each instance of the class gets a hidden field containing a pointer to the 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 the <code>IOffset</code> field of the corresponding <code>TInterfaceEntry</code>. At runtime, the <code>TObject.InitInstance</code> 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 <code>TObject.InitInstance</code> immediately exits when it encounters FPC_EMPTYINTF. This way, we yield higher constuction speed for the 'normal' (non-interfaced) objects.
+
FPC specifics: we use a special value of FPC_EMPTYINTF as a value for the class interface table whenever there compiler can determine that neither the class itself nor any of its ancestors implement any interfaces. FPC_EMPTYINTF is simply a pointer to an interface table with zero entries. Its difference to a nil pointer is that <code>TObject.InitInstance</code> immediately exits when it encounters FPC_EMPTYINTF. This way, we yield higher construction speed for the 'normal' (non-interfaced) objects.
  
 
Delphi specifics: a hidden field is also added for interfaces delegated to class-type properties.
 
Delphi specifics: a hidden field is also added for interfaces delegated to class-type properties.
Line 48: Line 56:
 
== GetInterface function and the 'as' operator ==
 
== GetInterface function and the 'as' operator ==
  
The semantic of casting a class to an interface using the 'as' operator should be as follows:
+
The semantics of casting a class to an interface using the 'as' operator should be as follows:
* For COM interfaces, reference counter is increased by one;
+
* For COM interfaces, the reference counter is increased by one;
* If the typecast is not possible, a EInvalidTypecast exception is raised.
+
* If the typecast is not possible, an EInvalidTypecast exception is raised.
  
Using 'as' operator in text of the program typically ends up in a call to <code>TObject.GetInterface</code> method, unless the class overrides the standard behavior, e.g. by providing a customized QueryInterface method. For Corba interfaces, <code>TObject.GetInterfaceByStr</code> method is called instead, specifying the string identifier of the interface rather than its GUID.
+
Using the 'as' operator in the text of the program typically ends up in a call to the <code>TObject.GetInterface</code> method, unless the class overrides the standard behavior, e.g. by providing a customized QueryInterface method. For Corba interfaces, the <code>TObject.GetInterfaceByStr</code> method is called instead, specifying the string identifier of the interface rather than its GUID.
  
 
== Typecasting class to interface ==
 
== Typecasting class to interface ==
Line 58: Line 66:
  
 
== Calls to interface methods ==
 
== Calls to interface methods ==
 +
 +
From the caller's point of view, the calling method of an interface looks exactly like calling a virtual method of a class/object (''TODO: add more details on this'').
 +
However, the VMT slots of an interface point to the special code called "interface wrappers". The compiler generates a wrapper for every procedure of every interface implemented directly by class (Delphi also generates wrappers for interfaces that are delegated to class properties).
 +
The purpose of wrappers is to adjust the implicit 'Self' parameter so it points back to the implementing class, and then jump to the actual implementing procedure.
 +
 +
<!--[[Category:Object-oriented development]]
 +
[[Category:FPC]]
 +
[[Category:FPC development]]
 +
[[Category:Delphi]]-->

Latest revision as of 13:14, 28 December 2020

English (en)

back to contents FPC internals

This page describes FPC implementation of interface delegation, and its differences 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 a table which is accessible at runtime with the TObject.GetInterfaceTable function. This function returns a pointer to a TInterfaceTable record, which contains a 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 a TInterfaceEntry (alignment 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, see below }
  IIDStr  : pshortstring; { never nil. For COM interfaces, this is upper(GuidToString(IID^)) }
  IType   : TInterfaceEntryType;
end;

InterfaceEntryType = (
  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 }
  etStaticMethodResult,   { Delegated to property of type interface, which is accessed by static method }
                          {   IOffset is direct address of the accessor method }
  etFieldValue,           { Delegated to property of type interface, which is accessed by field }
                          {   IOffset is offset from start of object instance to accessor field }
  etVirtualMethodClass,   { Same as etVirtualMethodResult, but accessor property has class type }
  etStaticMethodClass,    { Same as etStaticMethodResult, but accessor property has class type }
  etFieldValueClass       { Same as etFieldValue, but accessor property has class type }
);

Delphi uses a different TInterfaceEntry structure. Since Delphi does not support Corba interfaces, it does not have an IIDStr counterpart. More importantly, interfaces delegated to class-type properties are stored by Delphi as 'directly implemented', and there's no easy way for runtime to determine whether they are accessed by field or by method.

Instance changes

For interfaces implemented directly by a class, each instance of the class gets a hidden field containing a pointer to the 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 the 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 the class interface table whenever there compiler can determine that neither the class itself nor any of its ancestors implement any interfaces. FPC_EMPTYINTF is simply a pointer to an 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 construction 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

The semantics of casting a class to an interface using the 'as' operator should be as follows:

  • For COM interfaces, the reference counter is increased by one;
  • If the typecast is not possible, an EInvalidTypecast exception is raised.

Using the 'as' operator in the text of the program typically ends up in a call to the TObject.GetInterface method, unless the class overrides the standard behavior, e.g. by providing a customized QueryInterface method. For Corba interfaces, the TObject.GetInterfaceByStr method is called instead, specifying the string identifier of the interface rather than its GUID.

Typecasting class to interface

Calls to interface methods

From the caller's point of view, the calling method of an interface looks exactly like calling a virtual method of a class/object (TODO: add more details on this). However, the VMT slots of an interface point to the special code called "interface wrappers". The compiler generates a wrapper for every procedure of every interface implemented directly by class (Delphi also generates wrappers for interfaces that are delegated to class properties). The purpose of wrappers is to adjust the implicit 'Self' parameter so it points back to the implementing class, and then jump to the actual implementing procedure.