Jump to content

Recommended Posts

Hi,

 

I'm experimenting some interface design and came up with the following:

 

I would like to query a BaseClass instance if it supports a given interface, without freeing the class!
As the example below, my great grand child class can support an interface, but I don't want to include the class definition ( great grand child class ) in the Base class..
So, by defining interfaces, I can write exactly what I want: TMyFrame = Class( TBaseFrame, {SupportedInterfaces})

In the code below

procedure TForm22.Button1Click(Sender: TObject);
begin
   if Supports( fBaseFrameClass, ISupportTask ) then
      (fBaseFrame as ISupportTask).CheckTask;
end;

 

No matter what overload Support method I call, fBaseFrame is freed once the method exits.
Is there a way to know if my class instance supports an interface and call the corresponding method without messing ref counting and free the class instance prematurely?

 

 

unit frm.main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
 ISupportTask = Interface
    ['{3F785F20-7C44-4163-B55C-5EA267C7204E}']
    function CheckTask : Boolean;
 end;

 ISupportList = Interface
  ['{9B7D95E1-DA96-475A-9A1C-910CAF99E5F5}']
  function ListCount : Integer;
 End;

 ISupportItem = Interface
  ['{ACADFA2F-3500-4ACB-8049-F49FAC38EFB2}']
  function SaveItem : Boolean;
 End;



  TBaseFrame = Class( TInterfacedObject )
  protected
     fDummy : Boolean;
  End;

  TBaseFrameClass = Class of TBaseFrame;


  TMyFrame = Class( TBaseFrame, ISupportTask, ISupportList, ISupportItem )
  public

    function CheckTask : Boolean;
    function ListCount : Integer;
    function SaveItem  : Boolean;
    destructor Destroy; override;
  End;



  TForm22 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    fBaseFrameClass : TBaseFrameClass;
    fBaseFrame : TBaseFrame;
  public
    { Public declarations }
  end;

var
  Form22: TForm22;

implementation

{$R *.dfm}

procedure TForm22.Button1Click(Sender: TObject);
begin
   if Supports( fBaseFrameClass, ISupportTask ) then
      (fBaseFrame as ISupportTask).CheckTask;

end;

{ TMyFrame }

function TMyFrame.CheckTask: Boolean;
begin
   Result := True;
end;

destructor TMyFrame.Destroy;
begin
  fDummy := True;
  inherited;
end;

function TMyFrame.ListCount: Integer;
begin
  Result := 42;
end;

function TMyFrame.SaveItem: Boolean;
begin
  Result := False;
end;

procedure TForm22.FormCreate(Sender: TObject);
begin
   fBaseFrameClass := TMyFrame;
   fBaseFrame := fBaseFrameClass.Create;
end;

procedure TForm22.FormDestroy(Sender: TObject);
begin
  fBaseFrame.Free;
end;

end.

 

Share this post


Link to post

You are experiencing classic issue of "mixing objects and interfaces"

 

Your TBaseFrame class has enabled reference counting and you are using it like normal class. You should store it in interface reference - fBaseFrame: IInterface (or whatever other interface type suits you the best) and you should not call Free on it (you cannot call it on interface, but point is that its memory will be automatically handled so you don't have to worry about it)

 

https://dalijap.blogspot.com/2018/03/dont-mix-objects-and-interfaces.html

Edited by Dalija Prasnikar
  • Like 2

Share this post


Link to post

Although I don't particularly like this design, you can make a descendant of TInterfacedPersistent, your class will not be arc and will be able to implement interfaces.

TBaseFrame = class(TInterfacedPersistent)

 

Share this post


Link to post

Leaving aside the issues that are commented on by other, instead of 

if Supports(fBaseFrameClass, ISupportTask) then
  (fBaseFrame as ISupportTask).CheckTask;

surely you need

var
  Task: ISupportTask;
...
if Supports(fBaseFrame, ISupportTask, Task) then
  Task.CheckTask;

Two changes:

  1. Ask the implementing object rather than the class whether or not the interface is supported.
  2. Use the three argument overload of Supports so that you can get the interface while you are querying for it.
Edited by David Heffernan
  • Like 5

Share this post


Link to post
27 minutes ago, vfbb said:

Although I don't particularly like this design, you can make a descendant of TInterfacedPersistent, your class will not be arc and will be able to implement interfaces.


TBaseFrame = class(TInterfacedPersistent)

 

Or better inherit from TSingletonImplementation (I wonder who decided to give the base non-ARC interfaced object this misleading name...)

Share this post


Link to post

... or simply add the _AddRef, _Release (wasn't there a 3rd one?) methods required for an interface implementation to your ancestor class and let them do nothing.

  • Like 1

Share this post


Link to post
59 minutes ago, dummzeuch said:

... or simply add the _AddRef, _Release (wasn't there a 3rd one?) methods required for an interface implementation to your ancestor class and let them do nothing.

The third one is QueryInterface:

....

 protected
    function QueryInterface({$IFDEF FPC}constref{$ELSE}const{$ENDIF} IID: TGUID;  out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;

....


function TMyClass.QueryInterface({$IFDEF FPC}constref{$ELSE}const{$ENDIF}  IID: TGUID; out Obj): HResult; stdcall;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

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

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

Implement these three functions and you have a nice class with interfaces and without any reference counting.

Share this post


Link to post
4 hours ago, Dalija Prasnikar said:

You are experiencing classic issue of "mixing objects and interfaces"

 

Your TBaseFrame class has enabled reference counting and you are using it like normal class. You should store it in interface reference - fBaseFrame: IInterface (or whatever other interface type suits you the best) and you should not call Free on it (you cannot call it on interface, but point is that its memory will be automatically handled so you don't have to worry about it)

 

https://dalijap.blogspot.com/2018/03/dont-mix-objects-and-interfaces.html

Hi Dalija,

 

My class implements several interfaces, I want to check which interfaces are implemented and call the corresponding methods using some parameters. I wasn't expecting that Supports would also have this "side effect", although the documentation is clear about the destroying part...
 

Share this post


Link to post
4 hours ago, David Heffernan said:

Leaving aside the issues that are commented on by other, instead of 


if Supports(fBaseFrameClass, ISupportTask) then
  (fBaseFrame as ISupportTask).CheckTask;

surely you need


var
  Task: ISupportTask;
...
if Supports(fBaseFrame, ISupportTask, Task) then
  Task.CheckTask;

Two changes:

  1. Ask the implementing object rather than the class whether or not the interface is supported.
  2. Use the three argument overload of Supports so that you can get the interface while you are querying for it.

Hi David,

 

I tried every Support overload with the same effect. As it says in the documentation, there's a big Warning explaining exactly what I'm experience.
Either I forget about freeing the class, which is Ok because I will always implement some interface in my Classes, or I'll have to change overload the reference count methods.
I was hoping for a third choice..
 

Share this post


Link to post
3 hours ago, dummzeuch said:

... or simply add the _AddRef, _Release (wasn't there a 3rd one?) methods required for an interface implementation to your ancestor class and let them do nothing.

I'm considering this implementation... I was hoping there was another way.

Share this post


Link to post
41 minutes ago, Clément said:

My class implements several interfaces, I want to check which interfaces are implemented and call the corresponding methods using some parameters. I wasn't expecting that Supports would also have this "side effect", although the documentation is clear about the destroying part...

As long as your variable is some interface type, reference counting will be properly initialized and you don't have to worry about memory management of Support destroying your instance.

 

Code @David Heffernan posted for calling Support function is better one than your example. But this was not the root cause of your problems.

 

You can also disable reference counting mechanism with the code @Alexander Elagin posted. In this case you have to Free the instance when you are done, but there is another catch, you need to make sure that at that point you don't have any active interface references to your object because automatic _Release that compiler inserts after that interface reference goes out of scope will access already destroyed object instance - which can lead to a crash.

  • Like 1

Share this post


Link to post
1 hour ago, Clément said:

My class implements several interfaces, I want to check which interfaces are implemented and call the corresponding methods using some parameters.

Which is perfectly fine.

Quote

I wasn't expecting that Supports would also have this "side effect", although the documentation is clear about the destroying part...

This is not a side effect of Supports() itself, but rather is due to your mismanagement of your interfaced object.  Your TMyFrame class has reference counting enabled on it, but your fBaseClass member is not maintaining an active interface reference to the object, so the object has an initial reference count of 0 instead of 1, and then your cast to ISupportTask increments the reference count to 1 instead of 2, and then releasing that ISupportTask decrements the reference count to 0 instead of 1, freeing the object.

 

As Dalija said, you need to change this line:

fBaseFrame : TBaseFrame;

To this instead:

fBaseFrame : IInterface;

And then this change this:

procedure TForm22.FormDestroy(Sender: TObject);
begin
  fBaseFrame.Free;
end;

To this instead:

procedure TForm22.FormDestroy(Sender: TObject);
begin
  fBaseFrame := nil;
end;

Or, simply get rid of the OnDestroy handler completely, since the fBaseFrame member will be finalized anyway then the TForm22 object is destroyed, releasing the reference for you.

 

And, as David mentioned, you should change this:

procedure TForm22.Button1Click(Sender: TObject);
begin
  if Supports( fBaseFrameClass, ISupportTask ) then
    (fBaseFrame as ISupportTask).CheckTask;
end;

To this instead:

procedure TForm22.Button1Click(Sender: TObject);
var
  Task: ISupportTask;
begin
  if Supports( fBaseFrame, ISupportTask, Task ) then
    Task.CheckTask;
end;

 

Edited by Remy Lebeau
  • Like 3

Share this post


Link to post

Quick fix: keep both.

  TForm22 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FBaseFrameClass: TBaseFrameClass;

    { hold on to both }
    FBaseFrame: TBaseFrame; // object reference
    FBaseFrameInterface: IInterface; // interface reference
  end;

procedure TForm22.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;

  { Happy mixing is possible! }
  fBaseFrameClass := TMyFrame;
  FBaseFrame := FBaseFrameClass.Create;
  FBaseFrameInterface := FBaseFrame;
end;

procedure TForm22.FormDestroy(Sender: TObject);
begin
//  fBaseFrame.Free;
end;

 

  • Like 1

Share this post


Link to post

Hi Remy,

 

Thank you very much for your insight! I learn something new every day!
I will do some testing and get back.. This subject is cool!

Share this post


Link to post

Hi Gustav,

 

The sample code and example program you provided are helping me a lot!

 

Thanks you!

Share this post


Link to post
10 hours ago, Gustav Schubert said:

Quick fix: keep both.

You could do that, but that is not a very good design choice.  You would have to remember to always update BOTH references when updating EITHER reference.  That risks the two references getting out of sync.

 

If you absolutely need access to TMyFrame members that are not accessible via interface methods (which kind of defeats the purpose of using interfaces), you can always type-cast an interface back to TMyFrame when needed (D2010+), eg:

var
  Intf: ISomeInterface;

Intf := TMyFrame.Create;
...
Use (Intf as TMyFrame).SomeMember as needed...
...

 

  • 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

×