-1

The below code does not work.

program Project7;    
{$APPTYPE CONSOLE}
{$R *.res}

uses System.SysUtils;

type
  I1 = interface
    ['{B4BF44AD-23A9-4F42-BA2B-6E137E22344E}']
    procedure Test1;
  end;

  I2 = interface(I1)
    ['{AAAAAAAA-23A9-4F42-BA2B-BBBBBBBBBBBB}']
    procedure Test2;
  end;

  T12 = class(TInterfacedObject, I2)
  public
    procedure Test1;
    procedure Test2;
  public
    function MeAsI1: I1;
    function MeASI2: I2;
  end;

function T12.MeAsI1: I1;
begin
  Result:= (self as I1);
end;

function T12.MeASI2: I2;
begin
  Result:= (self as I2);
end;

procedure T12.Test1;
begin
 Writeln('T12: test1 from interface i1');
end;

procedure T12.Test2;
begin
   Writeln('T12: test2 from interface i2');
end;

Implemenation:

var
  MyClass: T12;
  AI1: I1;
  AI2: I2;
begin
  MyClass:= T12.Create;
  AI2:= MyClass.MeAsI2;
  AI2.Test2;
  Readln;
  AI1:= MyClass.MeAsI1;    //<< Exception interface not supported
  AI1.Test1;
  Readln;
end.

It gives an Exception: interface not supported.
It looks like interface inheritance does not really work like class inheritance.
If I add the interface I1 to class T12 it does work, but this gets a bit silly when a class implements many interfaces.
Is there a way to only declare I2 and still be able to return a reference to I1 from inside the class?
I'm using Delphi XE6, but I'm sure the error is the same in Delphi 3.

EDIT
A use case for this would be:

 IReadOnly = interface
   function GetSomething: integer;
   ...

 IRWIntf = interface(IReadOnly)
   procedure SetSomething(value: integer);
   ....
Johan
  • 74,508
  • 24
  • 191
  • 319
  • Your I2 is missing GUID – ain Jul 27 '14 at 14:24
  • As AI2 is declared as I2 and I2 misses a declaration for MeAsI1, this shouldn't even compile. – Uwe Raabe Jul 27 '14 at 14:26
  • No. The class does not implement `I1`. Check for yourself with a call to Supports? – David Heffernan Jul 27 '14 at 15:26
  • Copy-paste, I2 did not have a GUID, was planning to change a digit, but got distracted. – Johan Jul 27 '14 at 16:41
  • Ok. Thought so. In due course you'll accept that your class does not implement `I1`. – David Heffernan Jul 27 '14 at 16:44
  • @Johan Please check this thread, especially dthorpe's comments. http://stackoverflow.com/questions/4380002 – SOUser Jul 27 '14 at 17:51
  • @XichenLi, the documented behaviour is a workaround for a bug in OLE/COM. MS refused to fix it because Word & Excel depend on the bug, see: http://edn.embarcadero.com/article/29779 – Johan Jul 27 '14 at 20:41
  • Perhaps you need a question update to recognise that the class does not implement `I1`. – David Heffernan Jul 27 '14 at 21:28
  • To all involved, thanks for the explanation. I learned a lot about **how** interfaces work under the hood. @SirRufo answer shows a nice workaround for Borland's workaround of the IClassFactory2 bug. XichenLi's link to the David and John's acticle from wayback explains the **why**. – Johan Jul 28 '14 at 09:59

2 Answers2

4

This is by design. You can only retrieve those interfaces from a class that are explicitely mentioned in the class declaration.

Regarding your example: as I2 is inherited from I1, you can call the methods from I1 directly from AI2 as well as you can pass AI2 everywhere an interface of I1 is expected.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Yes, I know, but I like to have an limited interface and an extended interface that exposes more `private` functionality. To outside users I return the limited interface and to internal classes I expose the extended interface. – Johan Jul 27 '14 at 14:37
  • Thanks, the by design part makes sense. – Johan Jul 27 '14 at 14:37
2

Interface inheritance follows the logic of interfaces. With an interface you define the declaration without any implementation. So the inherited interface has the same declaration and nothing more and therefore there will be no implementation inheritance.

But you can do the following

procedure UseI1( AI1 : I1 );
begin
  AI1.Test1;
end;

procedure UseI2( AI2 : I2 );
begin
  AI2.Test1;
  AI2.Test2;
  UseI1( AI2 ); // use the inherited interface
end;

If you want your sample class to output the I1 then you have to change the method

function T12.MeAsI1: I1;
var
  LI2 : I2;
begin
  LI2 := Self;
  Result := LI2;
end;

But you have to be careful with that. Think of a class implementing both I1 and I2

TImplementor = class( TInterfacedPersistent, I1, I2 )
private
  procedure I1_Test1;
  procedure I2_Test1;
  procedure I2_Test2;

  procedure I1.Test1 = I1_Test1;
  procedure I2.Test1 = I2_Test1;
  procedure I2.Test2 = I2_Test2;
end;

procedure TImplementor.I1_Test1;
begin
  Writeln( 'I1_Test1' );
end;

procedure TImplementor.I2_Test1;
begin
  Writeln( 'I2_Test1' );
end;

procedure TImplementor.I2_Test2;
begin
  Writeln( 'I2_Test2' );
end;

Now we can use this

var
  LImplementor : TImplementor;
begin
  LImplementor := TImplementor.Create;
  try
    UseI1( LImplementor );
    // Output
    // I1_Test1
    UseI2( LImplementor );
    // Output
    // I2_Test1
    // I2_Test2
    // I2_Test1 !!!!!
  finally
    LImplementor.Free;
  end;
end;

Although we call UseI1 two times, we got two different outputs, because two different methods were called.

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • And where did I mention the *redeclaration* inside `I2`? – Sir Rufo Jul 27 '14 at 15:26
  • I just rearrange the answer – Sir Rufo Jul 27 '14 at 15:28
  • A, I see your point, you can force the object to have a different implementation for I1 as for I2's inherited I1. That looks like a recipe for confusion, but I can see what you mean. – Johan Jul 27 '14 at 16:48
  • Thanks, your example works. However in my code I'll use Uwe's solution. – Johan Jul 27 '14 at 16:50
  • Why are you talking about implementation inheritance? I don't think that's relevant. Although it's pretty weird that the class does not implement `I1`, and yet an `I2` is an `I1`. I did not know about that nuance. Must only be so at compile time. Runtime type checking with Supports etc. won't recognise that. – David Heffernan Jul 27 '14 at 22:09
  • 1
    @David the class only *supports* the implemented interfaces. If one of the implemented - supported - interfaces is inherited from another, you can use this also as the interface interface iherited from, but not direct from the class. In this case implementing I2 does not implement the inherited I1. But after getting I2 you use it also as an I1. My TImplementor sample also should show the difference between that and direct implementation of I1 – Sir Rufo Jul 27 '14 at 22:56