Z.B.
Z.B.

Reputation: 1205

Why interface table is not generated for the child class?

I have following problem:

For the child class the interface table is not generated at all.

type
  ITest = interface
    ['{69068A88-6712-40E0-B1E3-DA265F7428EA}']
    procedure Test;
  end;

  TBase = class(TInterfacedObject, ITest)
  protected
    procedure Test; virtual;
  public
    constructor Create;
  end;

  TChild = class(TBase)
  protected
    procedure Test; override;
  end;

constructor TBase.Create;
begin
  Assert(GetInterfaceTable <> nil);
end;

so when using following construction:

var
  X: ITest;
begin
  X := TChild.Create;
end;

I get the assertion failed.

So I do know that I need to re-declare the interfaces in the class declaration to fix this problem. But is that a language feature or an compiler old-old issue?

Because at compile time the compiler knows that the TChild is implementing the ITest interface. But once we go to run-time I do need to make duplicate re-declaration of the interfaces from the base! Why should we do that? For me it looks buggy.

Upvotes: 3

Views: 264

Answers (2)

Arnaud Bouchez
Arnaud Bouchez

Reputation: 43033

It is as designed.

AFAIR GetInterfaceTable is a very low-level method, which works only at a given class level. You should not have to use this method in your code, unless you are messing with the low-level RTTI information... but in all cases, you should better not use it.

Here how it is implemented:

class function TObject.GetInterfaceTable: PInterfaceTable;
begin
  Result := PPointer(PByte(Self) + vmtIntfTable)^;
end;

So you would have to check the parent classes types, too, using a recursive call or a loop.

For instance, here is a sample of its use in System.pas:

class function TObject.InitInstance(Instance: Pointer): TObject;
var
  IntfTable: PInterfaceTable;
  ClassPtr: TClass;
  I: Integer;
begin
  FillChar(Instance^, InstanceSize, 0);
  PPointer(Instance)^ := Pointer(Self);
  ClassPtr := Self;
  while ClassPtr <> nil do
  begin
    IntfTable := ClassPtr.GetInterfaceTable;
    if IntfTable <> nil then
      for I := 0 to IntfTable.EntryCount-1 do
        with IntfTable.Entries[I] do
        begin
          if VTable <> nil then
            PPointer(@PByte(Instance)[IOffset])^ := VTable;
        end;
    ClassPtr := ClassPtr.ClassParent;
  end;
  Result := Instance;
end;

Pretty low-level stuff, isn't it?

To implement an IoC pattern, you would rather have to use TObject.GetInterfaceEntry(), which does what you expect.

Upvotes: 1

Dalija Prasnikar
Dalija Prasnikar

Reputation: 28516

As documented GetInterfaceTable returns list of interface entries for the class. In your case that is TChild that does not have any interfaces implemented by itself.

Returns a pointer to a structure containing all of the interfaces implemented by a given class.

GetInterfaceTable returns the interface entries for the class. This list contains only interfaces implemented by this class, not its ancestors. To find the ancestor list, iteratively call ClassParent and then call GetInterfaceTable on the value it returns. To find the entry for a specific interface, use the GetInterfaceEntry method instead.

GetInterfaceTable is class function, just like ClassName is class function. It depends on instance class not on from which part of code you have called it:

If you run following code it will give you different ClassName regardless of the fact you are calling code in TBase constructor.

constructor TBase.Create;
begin
  writeln(ClassName);
end;

var
  x : ITest;

   X := TBase.Create; // outputs TBase
   X := TChild.Create; // outputs TChild

Upvotes: 2

Related Questions