Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2009-11

Interfaces via implements keyword

9 messages · 5 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Denis Golovan

Hi all

I've got a non-obvious class hierarchy with interfaces involved. The problem is that, I thought "property ... implements ...;" construction fully substitute for implementing interface methods in class itself, but now it's just not true.
I get different results in case implements is used and when it isn't.

If I comment implements implementation, my results are: TClassBase.Print 3
TClassBase.Print 3
TClassBase.Print 3

If I use implements, my results are:
TClassBase.Print 3
TClassBase.Print 3
TClassBase.Print 30

See my example below.

I wonder if it is by design?
I have similar bug posted on bug tracker under
http://bugs.freepascal.org/view.php?id=14842

======================================
program project1;
{$mode objfpc}{$H+}
type
  IIntf1 = interface
    ['{925B72C3-B47D-432A-AED8-544B3306055B}']
  end;
  IIntf2 = interface
    ['{E5C9BAC0-CE6D-42A8-BC85-F0AA51360E83}']
    procedure Print;
  end;
  { TClassBase }
  TClassBase = class(TInterfacedObject, IIntf2)
    Int:Integer;
    procedure Print;
  end;
  { TClass1 }
  TClass1 = class(TClassBase, IIntf1)
    public
      constructor Create;
  end;
  { TClass2 }
  TClass2 = class(TClassBase, IIntf1)
  private
    FIntf: IIntf1;
  public
    constructor Create;
    property Intf:IIntf1 read FIntf implements IIntf1; //COMMENT THIS LINE
  end;

{ TClassBase }

procedure TClassBase.Print;
begin
  WriteLn('TClassBase.Print ', Int);
end;
constructor TClass2.Create;
var O:TClass1;
begin
  Int:=3;
  O:=TClass1.Create;
  O.Int:=30;
  FIntf:=O;
end;

{ TClass1 }

constructor TClass1.Create;
begin
  Int:=1;
end;
var C2:TClass2;
    I1:IIntf1;
    I2:IIntf2;
begin
  C2:=TClass2.Create;
  C2.Print;
  I1:=C2 as IIntf1;
  I2:=C2 as IIntf2;
  I2.Print;
  I2:=I1 as IIntf2;
  I2.Print;
end.
======================================
Best regards, Denis Golovan
Florian Klaempfl
to Denis Golovan
Show quoted text 23 lines
Hi all I've got a non-obvious class hierarchy with interfaces involved. The problem is that, I thought "property ... implements ...;" construction fully substitute for implementing interface methods in class itself, but now it's just not true. I get different results in case implements is used and when it isn't. If I comment implements implementation, my results are: TClassBase.Print 3 TClassBase.Print 3 TClassBase.Print 3 If I use implements, my results are: TClassBase.Print 3 TClassBase.Print 3 TClassBase.Print 30 See my example below. I wonder if it is by design? I have similar bug posted on bug tracker under http://bugs.freepascal.org/view.php?id=14842

implements still might be buggy.

Michael Van Canneyt
to Florian Klaempfl
Florian Klaempfl wrote 26 lines
Denis Golovan schrieb: > Hi all > > I've got a non-obvious class hierarchy with interfaces involved. > The problem is that, I thought "property ... implements ...;" > construction fully substitute for implementing interface methods in > class itself, but now it's just not true. > I get different results in case implements is used and when it isn't. > > If I comment implements implementation, my results are: > TClassBase.Print 3 > TClassBase.Print 3 > TClassBase.Print 3 > > If I use implements, my results are: > TClassBase.Print 3 > TClassBase.Print 3 > TClassBase.Print 30 > > See my example below. > > I wonder if it is by design? > I have similar bug posted on bug tracker > under http://bugs.freepascal.org/view.php?id=14842 implements still might be buggy.

Not 'might', it definitely is buggy :(

Michael.

Denis Golovan
to Graeme Geldenhuys

Well.
Not good obviously.
Maybe somebody knows some workarounds?

On Mon, Nov 23, 2009 at 11:00 AM, Graeme Geldenhuys <

Show quoted text 23 lines
wrote: Michael Van Canneyt wrote: > > Not 'might', it definitely is buggy :( +1 Regards, - Graeme - -- fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/ _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Best regards, Denis Golovan
Andrew Hall
to Denis Golovan: Hi all I've got a non-obvious class hierarchy with interfaces involved. The problem is…

This code is working correctly/logically - and the same as it does in Delphi...

When using "implements" you are directing that this property "Intf" provides the interface "IIntf1" for your host object "TClass2" - and in TClass2.Create you create object "O: TClass1" to implement interface "IIntf1". Since "I1" requests the "IIntf1" interface, it is provided via "implements" and is therefore implemented by object "O".

When "I2:=C2 as IIntf2;" is executed, there is no "implements" property for this interface, so C2.GetInterface returns an "IIntf2" interface which it implements itself - and therefore "I2.Print" prints 3.
When "I2:=I1 as IIntf2;" is executed, this calls O.GetInterface (as "O" is the implementing object) requesting "IIntf2", which O does support, so the returned "IIntf2" interface is still implemented by "O".  When calling "I2.Print", "O" as the implementing object returns 30.

When you comment out "implements", the only object implementing the interfaces you are working with is your main "C2" object - so 3 is the only value that can be returned.

Regards,

Andrew Hall.

Denis Golovan wrote 19 lines
I've got a non-obvious class hierarchy with interfaces involved. The problem is that, I thought "property ... implements ...;" construction fully substitute for implementing interface methods in class itself, but now it's just not true. I get different results in case implements is used and when it isn't. If I comment implements implementation, my results are: TClassBase.Print 3 TClassBase.Print 3 TClassBase.Print 3 If I use implements, my results are: TClassBase.Print 3 TClassBase.Print 3 TClassBase.Print 30 See my example below. I wonder if it is by design? I have similar bug posted on bug tracker under http://bugs.freepascal.org/view.php?id=14842
Denis Golovan
to Andrew Hall

Thanks for info.

Well. Now I see the technical reason for it.
I thought so too.

Sure, I'm not a guru in compiler construction, but I'd like to know.

Is this a logic by design that I easily get into a situation when querying interfaces is not "transitive".
I mean, having one of class interfaces I get another interface using which I cannot query the first one anymore.

Doesn't it defeat the purpose of interfaces? Seems like instead of staying away of class internals, I am forced to know the underlying details.

See example below. The same commenting again gives another results. The last Print call just says that interface is not supported (btw, in Delphi also) if implemented using "implements".

type
  IIntf1 = interface
    ['{925B72C3-B47D-432A-AED8-544B3306055B}']
  end;
  IIntf2 = interface
    ['{E5C9BAC0-CE6D-42A8-BC85-F0AA51360E83}']
    procedure Print;
  end;
  { TClassBase }
  TClassBase = class(TInterfacedObject{, IIntf2})
    Int:Integer;
    procedure Print;
  end;
  { TClass1 }
  TClass1 = class(TClassBase, IIntf1)
    public
      constructor Create;
  end;
  { TClass2 }
  TClass2 = class(TClassBase, IIntf1, IIntf2)
  private
    FIntf: IIntf1;
  public
    constructor Create;
    procedure Print;
    property Intf:IIntf1 read FIntf implements IIntf1; //COMMENT THIS LINE
  end;

{ TClassBase }

procedure TClassBase.Print;
begin
  WriteLn('TClassBase.Print ', Int);
end;
constructor TClass2.Create;
var O:TClass1;
begin
  Int:=3;
  O:=TClass1.Create;
  O.Int:=30;
  FIntf:=O;
end;

{ TClass1 }

constructor TClass1.Create;
begin
  Int:=1;
end;
procedure TClass2.Print;
begin
  WriteLn('TClass2.Print ', Int);
end;
var C2:TClass2;
    I1:IIntf1;
    I2:IIntf2;
begin
  try
    C2:=TClass2.Create;
    C2.Print;
    I1:=C2 as IIntf1;
    I2:=C2 as IIntf2;
    I2.Print;
    I2:=I1 as IIntf2;
    I2.Print;
  except
    on E:Exception do
      WriteLn(E.Message);
  end;
  readln;
end.
Andrew Hall wrote 11 lines
This code is working correctly/logically - and the same as it does in Delphi... Regards, Andrew Hall. _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Best regards, Denis Golovan
Andrew Hall
to Denis Golovan
As you probably guess, the issue for the last line " I2:=I1 as IIntf2;" is that I1 is implemented by "O: TClass1" which no longer supports"IIntf2" - hence the error "interface not supported".
It would be nice if the class could know it is part of an "implements" structure and defer to the "parent" class - but by their very nature interfaces abstract the implementation so there is no way for the classes to know each other implicitly.  However - there are several ways that you can achieve this - two suggestions are (the first is simple and likely achieves what you need with no work):

1) Always reference your host class through an interface you know will be implemented by the host class, therefore calling "as" from this interface will always find any of the interfaces your class may support. The easiest would be to declare "I1: IUnknown" in the code below - I haven't tried it but that should work.

2) Store the host class reference in the other classes that implement the host and provide an "Owner: IUnknown" property in your "child" implementing classes to allow you to cast to any interface supported by the host class: "I2 := I1.Owner as IIntf2"

PS - normally your host class would not support the interface you are using "implements" to achieve... therefore the confusing circumstances of your first example would not normally happen - you'd be more likely to see this second example which is a little easier to decipher...

Regards,

Andrew Hall.

Denis Golovan wrote 31 lines
Thanks for info. Well. Now I see the technical reason for it. I thought so too. See example below. The same commenting again gives another results. The last Print call just says that interface is not supported (btw, in Delphi also) if implemented using "implements". var C2:TClass2; I1:IIntf1; I2:IIntf2; begin try C2:=TClass2.Create; C2.Print; I1:=C2 as IIntf1; I2:=C2 as IIntf2; I2.Print; I2:=I1 as IIntf2; I2.Print; except on E:Exception do WriteLn(E.Message); end; readln; end.
Denis Golovan
to Andrew Hall

Hi Andrew

Andrew Hall wrote 6 lines
1) Always reference your host class through an interface you know will be implemented by the host class, therefore calling "as" from this interface will always find any of the interfaces your class may support. The easiest would be to declare "I1: IUnknown" in the code below - I haven't tried it but that should work.

Yes. At first of this too.

Show quoted text 7 lines
2) Store the host class reference in the other classes that implement the host and provide an "Owner: IUnknown" property in your "child" implementing classes to allow you to cast to any interface supported by the host class: "I2 := I1.Owner as IIntf2"

And that's my current solution for some classes :)

PS - normally your host class would not support the interface you are using "implements" to achieve...

Normally yes. Unfortunately it's not my case.

Show quoted text 7 lines
therefore the confusing circumstances of your first example would not normally happen - you'd be more likely to see this second example which is a little easier to decipher... Regards, Andrew Hall.

But I think I found a better solution. The only way that needed to be changed is the way of QueryInterface works.
I just took the idea with Owner and redefined QueryInterface method to use it. Like following:

{ TInterfacedObj }

  TInterfacedObj = class(TObject, IUnknown)
    private
      FOwner:TInterfacedObj;
      function GetInterface(const iid : tguid;out obj):longint;
    public
      function QueryInterface(const iid : tguid;out obj) :
longint;virtual;stdcall;
      function _AddRef : longint;stdcall;
      function _Release : longint;stdcall;
      constructor Create(Owner:TInterfacedObj);
  end;
  { TInterfacedObj }
  const Err = HResult($80004002);
  function TInterfacedObj.GetInterface(const iid: tguid; out obj): longint;
  begin
    if inherited GetInterface(IID, Obj) then
      Result:=0
    else
      Result:=Err;
  end;
  function TInterfacedObj.QueryInterface(const iid: tguid; out obj):
longint;stdcall;
  begin
    WriteLn('QueryInterface ', ClassName);
    if FOwner = nil then
      Result:=GetInterface(iid, obj)
      else
      begin
        Result:=FOwner.QueryInterface(iid, obj);
        //try to find interface in itself
        if Result = Err then
           Result:=GetInterface(iid, obj);
      end;
  end;
  constructor TInterfacedObj.Create(Owner: TInterfacedObj);
  begin
    FOwner:=Owner;
  end;

Now it is possible to start interface search from the owner and only if owner has no appropriate interface, it queries itself. That's seems pretty nice solution.

Best regards, Denis Golovan