Clément 148 Posted February 2, 2021 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
Dalija Prasnikar 1396 Posted February 2, 2021 (edited) 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 February 2, 2021 by Dalija Prasnikar 2 Share this post Link to post
vfbb 285 Posted February 2, 2021 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
David Heffernan 2345 Posted February 2, 2021 (edited) 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: Ask the implementing object rather than the class whether or not the interface is supported. Use the three argument overload of Supports so that you can get the interface while you are querying for it. Edited February 2, 2021 by David Heffernan 5 Share this post Link to post
Alexander Elagin 143 Posted February 2, 2021 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
dummzeuch 1505 Posted February 2, 2021 ... 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. 1 Share this post Link to post
Alexander Elagin 143 Posted February 2, 2021 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
Clément 148 Posted February 2, 2021 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
Clément 148 Posted February 2, 2021 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: Ask the implementing object rather than the class whether or not the interface is supported. 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
Clément 148 Posted February 2, 2021 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
Dalija Prasnikar 1396 Posted February 2, 2021 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. 1 Share this post Link to post
Remy Lebeau 1394 Posted February 2, 2021 (edited) 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 February 2, 2021 by Remy Lebeau 3 Share this post Link to post
Gustav Schubert 25 Posted February 3, 2021 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; 1 Share this post Link to post
Clément 148 Posted February 3, 2021 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
Clément 148 Posted February 3, 2021 Hi Gustav, The sample code and example program you provided are helping me a lot! Thanks you! Share this post Link to post
Remy Lebeau 1394 Posted February 3, 2021 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... ... 1 Share this post Link to post