Search code examples
interfacefreepascaldelegation

Implementation through interface delegation not pass to descendant


Consider this interfaces and its implementations.

unit utest;

interface

{$MODE OBJFPC}

type

    IIntfA = interface
        procedure writeA();
    end;

    IIntfB = interface(IIntfA)
        procedure writeB();
    end;

    TADelegateClass = class(TInterfacedObject, IIntfA)
    public
        procedure writeA();
    end;

    TAClass = class(TInterfacedObject, IIntfA)
    private
        delegateA : IIntfA;
    public
        constructor create(const AInst : IIntfA);
        destructor destroy(); override;

        property A : IIntfA read delegateA implements IIntfA;
    end;

    TBClass = class(TAClass, IIntfB)
    public
        procedure writeB();
    end;

implementation

    procedure TADelegateClass.writeA();
    begin
        writeln('Implement IIntfA through delegation');
    end;

    constructor TAClass.create(const AInst : IIntfA);
    begin
        delegateA := AInst;
    end;

    destructor TAClass.destroy();
    begin
        inherited destroy();
        delegateA := nil;
    end;

    procedure TBClass.writeB();
    begin
        writeln('Implement IIntfB');
    end;

end.

Following program will not compile.

program test;

{$MODE OBJFPC}
uses
    utest;

var b : IIntfB;
begin
    b := TBClass.create(TADelegateClass.create());
    b.writeA();
    b.writeB();
end.

Free Pascal (version 3.0.4) complains

Error: No matching implementation for interface method "writeA;" found.

at line where TBClass is declared.

Of course, I can compile it successfully by implementing writeA either in TAClass or TBClass and call writeA method of TADelegateClass from there.

TAClass is concrete implementation of IIntfA interface through interface delegation but why TBClass, which is descendant of TAClass, is not considered a concrete implementation of IIntfA interface?


Solution

  • TAClass is concrete implementation of IIntfA interface through interface delegation but why TBClass, which is descendant of TAClass, is not considered a concrete implementation of IIntfA interface?

    Short answer: it's not IIntfA that is the problem, it is IIntfB that is incomplete.

    Long answer: Interface inheritance is C++ vtable inheritance, which is sometimes not intuitive.

    In the example:

    IIntfB = interface(IIntfA)
        procedure writeB();
    end;
    

    could actually be written as

    IIntfB = interface
        procedure writeA();
        procedure writeB();
    end;
    

    When implementing multiple interfaces, common parts are not reused. The compiler sets up individual tables from the implementing methods, such as:

    TADelegateClass:
       QueryInterface(IIntfA) =  Self.vtable_IIntfA
       vtable_IIntfA.writeA   <- Self.writeA
    
    TAClass:
       QueryInterface(IIntfA) =  delegateA.vtable_IIntfA
    
    TBClass:
       QueryInterface(IIntfA) =  inherited delegateA.vtable_IIntfA
       QueryInterface(IIntfB) =  vtable_IIntfB
       vtable_IIntfB.writeA   <- (this is missing!)
       vtable_IIntfB.writeB   <- Self.writeB
    

    TBClass does indeed not have an implementation of IIntfB.writeA. This can be verified by manually assigning a method to the specific interface and observe the error disappearing:

    TBClass = class(TAClass, IIntfB)
    public
        procedure IIntfB.writeA = writeB;
        // dummy method, shows IIntfB.writeA is missing
    

    Sadly, I don't know of any way to tell the compiler to access a mapping from another interface. FWIW, Delphi has the same bug/shortcoming.