Jump to content
Jacek Laskowski

The interfaces in Delphi are bad?

Recommended Posts

I got a Chad Z. Hower article:

 

https://www.codeproject.com/Articles/1252175/Fixing-Delphis-Interface-Limitations

 

The author is very critical about the interfaces in Delphi. But he gives examples where he mixes interface and object access, which is doomed to failure in advance.
I use interfaces very much, and I don't agree with the statement that there is more trouble with them than good.
I don't know e.g. C++ and I don't know how interfaces are solved there, probably my ignorance limits my understanding of the problem. Can someone enlighten me if the interfaces in Delphi are really that badly done?

 

Share this post


Link to post

I don't think they are badly done. The are just stuck in time...

Back in the day - you know, when COM was invented - interfaces where the go-to thing to get COM up and running in a Delphi project. Together with reference counting and GUIDs and stuff. Plus they allow for multiple inheritance. So, all ended there. Interfaces are feature complete... at least from a 90s POV.

In comes a person used to a 00s language such as C# and there you have it: "I need this", "I don't know why I have to do that" etc. :classic_biggrin: Programmer millenials...

Share this post


Link to post

Yes, interfaces in Delphi are troublesome.

 

From perspective of being tool for achieving abstractions they are commonly more trouble then they are worth. Chad explains some issues well. 

 

If they are used as COM interoperability, then there is nothing wrong with the because that was exactly their original purpose.

 

If you are looking on them as tool that can give you automatic memory management they are great, because you can more easily solve some otherwise complex problems that would require complex solutions. However, their use in such cases is limited and you cannot use them for such purpose everywhere. For instance you cannot add ARC to visual frameworks that are build on top of TComponent ownership model and automatically handle lifetime of your visual controls.

 

Main problem with interfaces is not that they are badly done per-se, but that they add duality to Delphi memory management and as such additional complexity and care is needed when you are using them. That is one of the reasons why I liked mobile ARC compiler. It showed that without that duality interfaces and objects can live happily ever after and that you don't have to look around your shoulder every time you use interfaces, expecting that they will kick you in behind.

 

Of course, full ARC brought other issues, but those were related to using existing frameworks that were not written with ARC in mind, so we got that dreadful DisposeOf...

 

No matter how you look at it, Delphi memory management is complex, sometimes too much... it is not question of being able to learn about things, but about how easy and how fast can you write your code, focusing on task at hand without needing to fiddle with all nuisances of handling your objects lifetime. 

  • Like 1

Share this post


Link to post

Delphi interfaces mix two concepts - ref-counting with auto disposal and the very interfaces as a required method set - into one type whereas frequently only one of them is needed. While ARC was bad idea because it broke too much old code, it had some sense. I guess if Delphi introduced new types like IPlainInterface and TRefCountedObject, it would be pretty nice and useful

Edited by Fr0sT.Brutal

Share this post


Link to post

I use interfaces all the time, and there are simple ways to make existing classes support interfaces with or without reference counting, without having to derive from a common base class like tInterfacedObject.

 

 

If I want to modify any class in such a way that it supports interfaces without reference counting, I simply add dummy methods _AddRef, _Release, and QueryInterface:

 

Type
tNewObject = class(TOldObject, IInterface)
  protected
    // IInterface
    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

function tNewObject._AddRef: Integer;
begin
  Result := -1;
end;

function tNewObject._Release: Integer;
begin
  Result := -1;
end;

function tNewObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

 

And similarly, it is possible to make any existing class support interfaces with reference counting, it just takes a few more methods: 

 


TNewObject = class(TOldObject, IInterface)
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;
  end;

procedure TNewObject.AfterConstruction;
begin
// Release the constructor's implicit refcount
  InterlockedDecrement(FRefCount);
  Inherited;
end;

procedure TNewObject.BeforeDestruction;
begin
  if RefCount <> 0 then
    Error(reInvalidPtr);
  Inherited;
end;

// Set an implicit refcount so that refcounting
// during construction won't destroy the object.

class function TNewObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TNewObject(Result).FRefCount := 1;
end;

function TNewObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TNewObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TNewObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

 

  • Like 2

Share this post


Link to post
41 minutes ago, A.M. Hoornweg said:

And similarly, it is possible to make any existing class support interfaces with reference counting, it just takes a few more methods: 

But try this "trick" on TComponent or it's descendants... no way.

Share this post


Link to post
14 minutes ago, Jacek Laskowski said:

But try this "trick" on TComponent or it's descendants... no way.

You shouldn't, because the component's owner manages its lifetime.  Refcounted objects do not have an owner, they manage their own lifetime. Freeing an object twice is a really bad idea.

Share this post


Link to post
1 hour ago, Jacek Laskowski said:

But try this "trick" on TComponent or it's descendants... no way.

Which is exactly one of the issues mentioned in article. 

 

Interface should be an abstraction that is always handled the same way, but implementation details of the particular class will leak and in some cases you will need to know how implementing class handles memory management. And then whole abstraction castle will just collapse.

Share this post


Link to post

Oh, come on. That is not really an issue, it's just a No-No. Just like not changing some Label on a form from a thread without synchronizing. Just don't do it, and you will not get into trouble.

Share this post


Link to post

I use interfaces and find them very effective -- in their place. But I curse the unimaginative implementation, which seems unlikely ever to be improved.

I understand that they came about to support COM. But I rarely make use of them in that context. Instead, they have become a tool for memory management, and for limiting exposure of class internals.

That said, it would be very nice to be able to make getters and setters in interfaces private. That you cannot do so makes the use of properties in interfaces almost silly. But I would prefer to expose the properties, and hide the underlying methods.

There has been much change since D3 in the ways most of us design our code, but the interface is like a fly captured in amber.

Share this post


Link to post
32 minutes ago, Sherlock said:

Oh, come on. That is not really an issue, it's just a No-No. Just like not changing some Label on a form from a thread without synchronizing. Just don't do it, and you will not get into trouble.

Yes, it is an issue.

 

Even if you ignore "just don't do it" part which you first have to know exists and then have to learn how to do properly in Delphi (not so easy as in Java, C# or other languages where you can use interfaces easily), main problem is that you cannot just have some IFoo interface and implement it on every class you like and use it equally. As long as all implementing classes handle ARC the same way, it is fine, but try having IFoo on TInterfacedObject descendant and TComponent descendant and you are basically screwed (or at least seriously limited in what you can do with such classes).

Share this post


Link to post

Would be maybe a good time and place to make some clear proposals howto improve interfaces, instead of moaning all the time.

So how exactly should they get fixed, so that all can be happy ?

Share this post


Link to post
4 minutes ago, Dalija Prasnikar said:

Even if you ignore "just don't do it" part which you first have to know exists and then have to learn how to do properly in Delphi

Is it unreasonable to expect that programmers have knowledge and skill?

  • Like 5

Share this post


Link to post
19 minutes ago, David Heffernan said:

Is it unreasonable to expect that programmers have knowledge and skill?

No,, but learning takes time.

 

I am not talking here about lazy people that don't want to learn, I am talking about efficiency of learning a language that has additional levels of complexity comparing to learning language that doesn't and where both languages are generally suitable for solving some problem. I know that knowledge accumulates over time, so you will not have to relearn things you know the next time around, but such things do have overall impact on productivity.

Share this post


Link to post
1 hour ago, Rollo62 said:

Would be maybe a good time and place to make some clear proposals howto improve interfaces, instead of moaning all the time.

So how exactly should they get fixed, so that all can be happy ?

Full ARC compiler on all platforms and getting rid of TComponent ownership model would be the only proper solution. But that would definitely not make everyone happy.

 

Maybe additional "interfaces" as abstraction that is not tied with memory management could solve most of the issues, but I never given that too much thought in terms of finding potential gotchas and additional levels of complexity such feature might add.

Share this post


Link to post
4 hours ago, Alexander Elagin said:

You mean something like CORBA interfaces as implemented as FreePascal? They are exactly this: a set of methods, not refcounted.

Yeah, they got it right. Though they have to keep Delphi compat. Could both interface types be used together?

 

4 hours ago, A.M. Hoornweg said:

I use interfaces all the time, and there are simple ways to make existing classes support interfaces with or without reference counting, without having to derive from a common base class like tInterfacedObject.

 

  

If I want to modify any class in such a way that it supports interfaces without reference counting, I simply add dummy methods _AddRef, _Release, and QueryInterface:

Very nice workaround!

Share this post


Link to post
2 minutes ago, Fr0sT.Brutal said:

Yeah, they got it right. Though they have to keep Delphi compat. Could both interface types be used together?

I guess yes (the {$INTERFACES} directive is local) but honestly I have not tried it myself because it is incompatible with Delphi code :classic_wacko:

Share this post


Link to post
28 minutes ago, Dalija Prasnikar said:

Full ARC compiler on all platforms and getting rid of TComponent ownership model would be the only proper solution. But that would definitely not make everyone happy.

 

Maybe additional "interfaces" as abstraction that is not tied with memory management could solve most of the issues, but I never given that too much thought in terms of finding potential gotchas and additional levels of complexity such feature might add.

Yes, Rx10.4 will move in the opposite direction then.

Maybe someone else has some practical thoughts too, how to get most of it.

I personally can live with the interface as is well, but of coarse the pitfalls are deep.

 

How about a better error-detection by the compiler/linker, would that be thinkable ?

Couldn't be all the do's and dont's in the compiler logic (as switchable error option of coarse, for those who need it as is) ?

Probably that not possible either, but maybe the most common, drastic mistakes/misuses could be turned into some clear compile time errors (warnings).

 

Share this post


Link to post
On 4/17/2020 at 1:31 PM, A.M. Hoornweg said:

You shouldn't, because the component's owner manages its lifetime.  Refcounted objects do not have an owner, they manage their own lifetime. Freeing an object twice is a really bad idea.

Very common, basically always, used database classes like TDataset, etc. I create in runtime, they are for example part of business classes, as "Owner" I give them nil. There I would like to use them in the interface model, not in the VCL management model.

Share this post


Link to post

There are plenty of object wrappers that allow using ref-counting with any object, and everyone could write his own one in 10 minutes (I did, and use it for temp datasets)

Share this post


Link to post
On 4/17/2020 at 5:24 PM, Rollo62 said:

How about a better error-detection by the compiler/linker, would that be thinkable ?

Couldn't be all the do's and dont's in the compiler logic (as switchable error option of coarse, for those who need it as is) ?

Probably that not possible either, but maybe the most common, drastic mistakes/misuses could be turned into some clear compile time errors (warnings).

There is nothing compiler can do here. Compiler does not have intrinsic knowledge how particular class implements reference counting and therefore cannot emit warnings in proper places because it does not know whether particular piece of code is correct or not.

Share this post


Link to post

@Dalija Prasnikar

Yes, I think compiler/linker could get a little smarter though, maybe in the direction like FixInsight or PascalAnalyser can do.

To find common anti-patterns, and give warnings, that should be not so far out of reach.

But you're right, maybe this is a task for some separate tool probably.

Edited by Rollo62
  • Like 1

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

×