Jump to content
Koru

Should Delphi have native interfaces?

Recommended Posts

Interface-oriented development is an awesome tool, and the ARC memory management that usually comes with it can be great as well.

 

But in our beloved Delphi, interfaces are not full citizens.


Working with Delphi interfaces becomes soon troublesome; the way they have been implemented makes impossible some of the common stuff we do in our daily programming


As an example, you can't use an interface method as an event handler. You can't use anything that expects a "procedure of object" or "function of object", nor use generics or other modern features.


Why? Because the interfaces we have in Delphi were introduced just and only to achieve COM integration. So, the interfaces don't know they always have an object (delphi class) behind them 


So we need a remake of Delphi interfaces, but we can't break the software already using them


Here comes the "native interface": probably they should have a distinction on their declaration, so the compiler knows to handle them


And the big difference using them, is that native interface methods are object methods, so the compiler would know how to handle them.


I would like this post to serve as brainstorming for those of you who have ideas about how to enable this division of the com-based interface and a possible new native implementation, implementation of which there may also be proposals for improvement.


For example, at least for me, it would be nice if interfaces (at least the native ones) could have multiple inheritance of interfaces:

  InterfaceTest = ninterface(Interface1, Interface2, Interface3)
    procedure Test;
  end;

Feel free to comment in any direction.

  • Like 2

Share this post


Link to post
Guest

Brain storming, huh.....  i have some still working.

 

How about reverse interface declaration, i mean let the compiler build base interfaces from classes

Like this

type
  TProcessor = class
  public
    constructor Create;
    procedure ProcessData(var Data); IMethod;   // IMethod will only be used when InterfaceOf is declared
    procedure CleanUp; IMethod;
  end;

  IProcessor = InterfaceOf(TProcessor);

  // IProcessor with the above one line declaration is identical to the following
  {
  IProcessor = interface
    procedure ProcessData(var Data);
    procedure CleanUp;
  end;
  }

  // and can be extended like this
  IPostProcessor = InterfaceOf(TProcessor)
    procedure NotifyFinish;
  end;

  // or like this
  TCompressor = class
  public
    procedure Compress; abstract; IMethod;
  end;

  IDataCompressor = Interface(InterfaceOf(TProcessor), IInterface, InterfaceOf(TCompressor));

 

Share this post


Link to post

Instead of IMethod I would suggest a new directive interfaced, optional with an alternative name used with InterfaceOf (similar to a method resolution clause).

type
  TProcessor = class
  public
    constructor Create;
    procedure ProcessData(var Data); interfaced;
    procedure CleanUp; interfaced(Clear); // InterfaceOf will publish this method as Clear
    procedure Clear; // used inside TProcessor
    property Value: Integer read GetValue; interfaced; // should work for properties, too
  end;

 

  • Like 1

Share this post


Link to post
2 hours ago, Kas Ob. said:

Brain storming, huh.....  i have some still working.

 

How about reverse interface declaration, i mean let the compiler build base interfaces from classes

Like this


type
  TProcessor = class
  public
    constructor Create;
    procedure ProcessData(var Data); IMethod;   // IMethod will only be used when InterfaceOf is declared
    procedure CleanUp; IMethod;
  end;

  IProcessor = InterfaceOf(TProcessor);

  // IProcessor with the above one line declaration is identical to the following
  {
  IProcessor = interface
    procedure ProcessData(var Data);
    procedure CleanUp;
  end;
  }

  // and can be extended like this
  IPostProcessor = InterfaceOf(TProcessor)
    procedure NotifyFinish;
  end;

  // or like this
  TCompressor = class
  public
    procedure Compress; abstract; IMethod;
  end;

  IDataCompressor = Interface(InterfaceOf(TProcessor), IInterface, InterfaceOf(TCompressor));

 

Hi, thank you very much for your proposals!

I talk about personal tastes, but I prefer to see at a glance the definition of the interface (contract), for me it's more difficult the way you present the new interface since you have to extract it mentally from different parts of the source code

 

Share this post


Link to post
Guest
13 minutes ago, Koru said:

I talk about personal tastes, but I prefer to see at a glance the definition of the interface (contract), for me it's more difficult the way you present the new interface since you have to extract it mentally from different parts of the source code

Sure, i simply always prefer the choice itself, that might reversed declaration for me is more maintainable and easier to tweak, such change can be in one place, but as you said it is personal taste.

 

In case such declaration been added to Delphi syntax, we can simply start nagging Uwe to add simple extract interface from class method, and viola you have your classic interface.

 

@Uwe Raabe I wrote interfaced at first then changed it, the word interfaced made me giggle a little, for my non native English ears it sounded like bunched in the face or smacked-faced 🙂 

Share this post


Link to post

Speaking about possible new features, it's a mess that the Supports function doesn't see any of the base interfaces of a possible inheriteance tree. Only the interfaces that are declared in the class header are visible to this function, so you are forced to write the interfaces in the header, as you see you need them here or there. So it would be nice that this kind of functions can work with base interfaces

Share this post


Link to post
5 minutes ago, Koru said:

Speaking about possible new features, it's a mess that the Supports function doesn't see any of the base interfaces of a possible inheriteance tree. Only the interfaces that are declared in the class header are visible to this function, so you are forced to write the interfaces in the header, as you see you need them here or there. So it would be nice that this kind of functions can work with base interfaces

I am not sure if I can support this request. It is a simplification to avoid writing code, but it should be well thought before implementing it. There might be uses cases when this is unwanted and when it is done this way you may have a hard time to get rid of it again. Perhaps I can think about a use case for the current state.

Share this post


Link to post
2 hours ago, Koru said:

... it's a mess that the Supports function doesn't see any of the base interfaces of a possible inheriteance tree. Only the interfaces that are declared in the class header are visible to this function ...

I think I have encountered this problem on rare occasions but I can't remember the circumstances. Did you mean interface inheritance?

For example the following works fine:

type
  IFoo = interface
    ['{F99BAE4A-6612-4714-96B4-237763239C7F}']
  end;

  IBar = interface
    ['{570FBD40-8ECF-4B4B-9898-EF3F4146FFF9}']
  end;

type
  TFoo = class(TInterfacedObject, IFoo)
  end;

  TBar = class(TFoo, IBar)
  end;

type
  TFooObject = class(TComponent, IFoo)
  end;

  TBarObject = class(TFooObject, IBar)
  end;

procedure Test;
begin
  // Supports(interface)
  var FooBar := TBar.Create as IUnknown;

  Assert(Supports(FooBar, IFoo));
  Assert(Supports(FooBar, IBar));

  // Supports(instance)
  var FooBarObject := TBarObject.Create(nil);

  Assert(Supports(FooBarObject, IFoo));
  Assert(Supports(FooBarObject, IBar));
end;

 

  • Like 1

Share this post


Link to post

If I am not mistaken, Koru is referring to a different case:

type
  IBar = interface
    ['{570FBD40-8ECF-4B4B-9898-EF3F4146FFF9}']
  end;

  IFooBar = interface(IBar)
    ['{1A99C9D3-CC94-4BCE-BF9C-354BF14EC5C7}']
  end;

type
  TFooBar = class(TInterfacedObject, IFooBar)
  end;

procedure Test;
begin
  var
  FooBarObject := TFooBar.Create;

  Assert(Supports(FooBarObject, IFooBar));
  Assert(Supports(FooBarObject, IBar));  //Fails!
end;

For me this is pretty logical, so nothing to be changed.

Edited by Uwe Raabe
  • Like 3

Share this post


Link to post
12 minutes ago, Uwe Raabe said:

If I am not mistaken, Koru is referring to a different case:


type
  IBar = interface
    ['{570FBD40-8ECF-4B4B-9898-EF3F4146FFF9}']
  end;

  IFooBar = interface(IBar)
    ['{1A99C9D3-CC94-4BCE-BF9C-354BF14EC5C7}']
  end;

type
  TFooBar = class(TInterfacedObject, IFooBar)
  end;

procedure Test;
begin
  var
  FooBarObject := TFooBar.Create;

  Assert(Supports(FooBarObject, IFooBar));
  Assert(Supports(FooBarObject, IBar));  //Fails!
end;

For me this is pretty logical, so nothing to be changed.

Hi, yes I was referring to something like this case you point.

Well, it may be that the approach was not very correct. My idea focused on a context where we can ask native interfaces for information similar to what we can ask to classes, such as for example: if it inherits from a certain base interface. And in that context I was handling the idea (perhaps wrong, we are talking about ideas and giving possibilities) that the Supports function was capable of giving that functionality

Share this post


Link to post
3 hours ago, Uwe Raabe said:

If I am not mistaken, Koru is referring to a different case:


type
  IBar = interface
    ['{570FBD40-8ECF-4B4B-9898-EF3F4146FFF9}']
  end;

  IFooBar = interface(IBar)
    ['{1A99C9D3-CC94-4BCE-BF9C-354BF14EC5C7}']
  end;

type
  TFooBar = class(TInterfacedObject, IFooBar)
  end;

procedure Test;
begin
  var
  FooBarObject := TFooBar.Create;

  Assert(Supports(FooBarObject, IFooBar));
  Assert(Supports(FooBarObject, IBar));  //Fails!
end;

For me this is pretty logical, so nothing to be changed.

Hi again, why do you see it logical? I think it seems quite illogical to ask that to this function and to answer that it's false when we know it's true

Share this post


Link to post

There is nothing logical about it - as the previously linked article says:

Quote

This requirement is an historical artifact of some OLE2 bugs with IClassFactory and IClassFactory2, and some other interfaces.

 

  • Like 1

Share this post


Link to post
12 minutes ago, Stefan Glienke said:
17 minutes ago, Stefan Glienke said:

There is nothing logical about it - as the previously linked article says:

 

Hi,

not having an extensive knowledge of the subject can lead me to erroneous statements, if so, do not hesitate to correct me. If we talk about a new implementation of the interfaces as native interfaces, no longer tied to COM, I understand that Embarcadero can make those limitations cease to exist, is what I say correct or is there something I am leaving out?

 

Share this post


Link to post
29 minutes ago, Koru said:

I think it seems quite illogical to ask that to this function and to answer that it's false when we know it's true

But it isn't true. The IFooBar interface inherits the methods of IBar - at compile time. The implementing object doesn't inherit the contract that it must implement the IBar interface.

 

17 minutes ago, Stefan Glienke said:

as the previously linked article says

I'm not convinced that John Kaster knew what he was talking about when he wrote that. I mostly included the link because it seems to be the earliest mention of the feature.

The limitation as-is matches my recollection of "the rules of COM" but I could be mistaken. I'm not up to reading the COM bible again just to find out. It was hard enough the first time(s). :classic_smile:

  • Like 1

Share this post


Link to post
4 minutes ago, Anders Melander said:

But it isn't true. The IFooBar interface inherits the methods of IBar - at compile time. The implementing object doesn't inherit the contract that it must implement the IBar interface.

According to that logic IFooBar must not be assignment compatible to IBar - but it is.

 

Even the compiler knows about this because actually if you implement IFooBar and IBar into the same class without any specific method resolution clauses it puts both IMTs into the very same slot (IBar shares it with the IFooBar one)

Edited by Stefan Glienke
  • Like 2

Share this post


Link to post
6 minutes ago, Anders Melander said:

But it isn't true. The IFooBar interface inherits the methods of IBar - at compile time. The implementing object doesn't inherit the contract that it must implement the IBar interface.

 

I'm not convinced that John Kaster knew what he was talking about when he wrote that. I mostly included the link because it seems to be the earliest mention of the feature.

The limitation as-is matches my recollection of "the rules of COM" but I could be mistaken. I'm not up to reading the COM bible again just to find out. It was hard enough the first time(s). :classic_smile:

I have the feeling that we are talking about how it works now (you correct me if I'm wrong), not how we would like it to work, and I talk about how I would like it to work.

In this specific case if the native interfaces have inheritance capacity, these possibilities would be viable, if they are clearly useful for the developers of course

Share this post


Link to post

"Native Interface" is a terrible name tbh - what exactly would be "native" about it that current interfaces are not.

 

A look at other languages might be helpful to actually define clearly what your desired requirements are - for example traits in Scala.

  • Like 4

Share this post


Link to post
8 minutes ago, Stefan Glienke said:

"Native Interface" is a terrible name tbh - what exactly would be "native" about it that current interfaces are not.

 

A look at other languages might be helpful to actually define clearly what your desired requirements are - for example traits in Scala.

Putting proper names always leads to headaches  :classic_smile:

And it is relevant, but it is not the goal of the post. The goal is to know what deficiencies or limitations we see to the current interfaces in Delphi, partly (or mostly) because they were born from COM.

By native interface we have been calling in the post to that interface that we know that behind it has a Delphi class, and under that assumption I would like things like the ones I list in the first post, that probably would be enough advance for me: 'As an example, you can't use an interface method as an event handler. You can't use anything that expects a "procedure of object" or "function of object", nor use generics or other modern features'
But I know that there are people like you who that have struggled a lot with the interfaces and I'm sure you have clearer limitations that perhaps I can't see.

 

 

Share this post


Link to post
38 minutes ago, Stefan Glienke said:

"Native Interface" is a terrible name tbh - what exactly would be "native" about it that current interfaces are not.

It's just a name, any name would do. This starting name aludes to the fact that such interface would be implemented by Delphi native classes, and that means a lot, as this way the compiler does not just know a method address, but the object instance. And that gives us full compatibility with anything currently implemented that works with class methods.

 

On the other side, there is already an embarcadero answer on Quality portal, acknowledging this is feasable, and would work as expected

 

My knowledge on interface-kind features on other languages is zero, so I wonder if there is something really useful that could improve the kind of interfaces we're suggesting, without making it overly difficult to implement and derailing this train

  • Like 1

Share this post


Link to post

What you describe is exactly what traits are in Scala. Well minus the fact that traits can even declare fields but when they always implemented by objects that would work as well - hence I would not call them anything like "so and so interface" - because while they might be similar they would be a completely different beast and not to be mixed with them.

 

Anyhow it would come down to no proper way to cleanup those things - Scala and all other languages that have similar things use GC so you will not have any headaches about when something has to be destroyed. If you pass around your objects as traits/ninterface/whatever with the memory management we currently have in Delphi this will just an even huger pain than you already have if you mix object and interface references to classes that either implement reference counting or not.

 

P.S. If you are interested how Scala implements traits: https://www.nurkiewicz.com/2013/04/scala-traits-implementation-and.html

Edited by Stefan Glienke
  • Like 1

Share this post


Link to post
2 hours ago, Stefan Glienke said:

According to that logic IFooBar must not be assignment compatible to IBar - but it is.

You are correct:

type
  IBar = interface
    ['{570FBD40-8ECF-4B4B-9898-EF3F4146FFF9}']
  end;

  IFooBar = interface(IBar)
    ['{F99BAE4A-6612-4714-96B4-237763239C7F}']
  end;

type
  TFooBar = class(TInterfacedObject, IFooBar)
  end;

procedure test;
begin
  var FooBar: IFooBar := TFooBar.Create;
  var Bar: IBar := FooBar;
end;

...but as we already know...

var FooBar: IFooBar := TFooBar.Create;
Assert(Supports(FooBar, IBar));

...will fail, so there's a bug somewhere - or at least a really bad inconsistency.

Supports just does a IUnknown.QueryInterface which ends up in TObject.GetInterface. The assignment on the other hand just copies the pointer and calls _AddRef.

From my understanding on what interface inheritance is in Delphi, Supports() is correct and the compiler is wrong but there are obviously different interpretations of this.

  • Like 1

Share this post


Link to post
5 minutes ago, Anders Melander said:

there's a bug somewhere

Yes! In the COM implementation and it was carried over to Delphi as a feature to make it work.

  • Like 1

Share this post


Link to post
39 minutes ago, Stefan Glienke said:

What you describe is exactly what traits are in Scala. Well minus the fact that traits can even declare fields but when they always implemented by objects that would work as well - hence I would not call them anything like "so and so interface" - because while they might be similar they would be a completely different beast and not to be mixed with them.



I'm not aware about Scala but is this like what java does with default implementation for interface ?

  • Like 1

Share this post


Link to post
42 minutes ago, Stefan Glienke said:

Yes! In the COM implementation and it was carried over to Delphi as a feature to make it work.

And that's why in a COM-free world we need more capable interfaces:

  1. Developed  for Delphi
  2. With true and working inheritance (or call it extension, if you wish)
  3. Always implemented by a Delphi Class and aware of it
  4. With methods that can be used anywhere a class method can be  used

I don't know if anything else is needed

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×