Anders Melander 1782 Posted September 16, 2022 Once again I'm struggling with the fact that the compilers Return Value Optimization of interfaces produces code that keeps interfaces alive beyond the scope of the block in which they are referenced. For example let's say that I have a function CreateFoo that creates a reference counted object and returns an interface to this object: begin var Foo := CreateFoo; ... Foo := nil; ... end; One would think it was reasonable to assume that, given no other references to the object besides the Foo variable, once I nill the Foo reference then the reference count goes to zero and the object is destroyed. Right?... Nope. It seems that what the compiler actually produces is something like this: var Gotcha: IUnknown; begin Gotcha := nil; try begin Gotcha := CreateFoo; var Foo := Gotcha as IUnknown; ... Foo := nil; ... end; finally Cotcha := nil; end; end; Since this is an old, known problem I wonder if anyone has been able to come up with a work around. I have mostly been able to work around it in my own code by simply not relying on reference counted objects being destroyed inside local blocks but I have a few places where I simply either have to make this work properly or abandon reference counting. FWIW, the following reproduces the problem: program FooFail; {$APPTYPE CONSOLE} {$R *.res} uses Classes; type TFoo = class(TInterfacedObject) public constructor Create; destructor Destroy; override; end; constructor TFoo.Create; begin inherited Create; WriteLn(' Create'); end; destructor TFoo.Destroy; begin WriteLn(' Destroy'); inherited; end; function CreateFoo: IUnknown; begin Result := TFoo.Create; end; begin WriteLn('Begin'); begin var Foo1: IUnknown := TFoo.Create; Foo1 := nil; // Foo1 destroyed here end; begin var Foo2: IUnknown := CreateFoo; Foo2 := nil; end; WriteLn('End'); end. // Foo2 destroyed here The expected desired output is: Begin Create Destroy Create Destroy End The actual output is: Begin Create Destroy Create End Destroy Share this post Link to post
Der schöne Günther 316 Posted September 16, 2022 With Delphi 11.1, my actual output is exactly what you expect. 1 Share this post Link to post
Anders Melander 1782 Posted September 16, 2022 2 minutes ago, Der schöne Günther said: With Delphi 11.1, my actual output is exactly what you expect. Thanks very much. Works in Delphi 11.0 too it seems. This was driving me absolutely crazy because I was so sure that it wasn't a problem anymore but I just couldn't get it working. Some of my projects are in Delphi 10.3 and some are in 11.0 so that explains the confusion. I'll bet that in a year or so I will be in this situation again but at least there's a chance I will find this thread then 🙂 Share this post Link to post
Darian Miller 361 Posted September 17, 2022 FWIW, 11.2 is also: Begin Create Destroy Create Destroy End 1 Share this post Link to post
Dalija Prasnikar 1396 Posted September 17, 2022 There are two separate concerns here. First, main application code block can exhibit some weird behavior for global variables declared there, including inline variables. If you want to make proper test of some behavior, I suggest wrapping the code in additional procedure. Second, inline variables had (and maybe still have) some problems and sometimes using them does not work as expected. If you encounter an issue, usually solution is to declare local variable instead of inline. Your text case does not work correctly in 10.3.3 when code runs in main code block. If moved to the procedure, then it behaves as it should. In 10.4.2 and and 11.2 it works correctly in all scenarios, so the issue was fixed in the meantime. 1 Share this post Link to post
Anders Melander 1782 Posted December 8, 2022 On 9/17/2022 at 11:36 AM, Dalija Prasnikar said: In 10.4.2 and and 11.2 it works correctly in all scenarios, so the issue was fixed in the meantime. Not quite fixed it seems 😞 Casting a function result with the interface as operator and the old behavior is back. Reproduced with Delphi 11.2. This works: begin var Foo := FunctionReturningInterface; // RefCount = 1 Foo := nil; // RefCount = 0 ... end; but this doesn't: begin var Foo := FunctionReturningInterface as IFoo; // RefCount = 2 Foo := nil; // RefCount = 1 ... end; // RefCount = 0 Workaround: begin var Foo: IFoo; begin var := TempFoo := FunctionReturningInterface; // RefCount = 1 Foo := TempFoo as IFoo; // RefCount = 2 end; // RefCount = 1 Foo := nil; // RefCount = 0 ... end; or function FooFunc: IFoo; begin Result := FunctionReturningInterface as IFoo; // RefCount = 2 end; // RefCount = 1 begin var Foo := FooFunc; // RefCount = 1 Foo := nil; // RefCount = 0 ... end; 1 Share this post Link to post
Der schöne Günther 316 Posted December 8, 2022 Shouldn't var Foo: IFoo := FunctionReturningInterface() suffice? 🤔 Share this post Link to post
Anders Melander 1782 Posted December 8, 2022 (edited) 49 minutes ago, Der schöne Günther said: Shouldn't var Foo: IFoo := FunctionReturningInterface() suffice? Not if FunctionReturningInterface returns an IBar interface or anything other than IFoo. The actual code is an object factory where I pass an interface ID (i.e. a GUID) to a method and get an instance that implements that interface back. Something like this: var MyDialog := DialogManager.CreateDialog(IMyDialog) as IMyDialog; // CreateDialog returns an IInterface MyDialog.Execute; DialogManager itself is also an interface so I can't use generics on it. If it had been an object then I could have done like this instead: var MyDialog := DialogManager.CreateDialog<IMyDialog>; // CreateDialog returns an IMyDialog MyDialog.Execute; Edited December 8, 2022 by Anders Melander Share this post Link to post
Keesver 23 Posted December 8, 2022 I think this should work: var F: IFoo; Supports(DialogManager.CreateDialog(IMyDialog), IFoo, F); Share this post Link to post
Anders Melander 1782 Posted December 8, 2022 1 minute ago, Keesver said: I think this should work: Yes, probably. There are plenty of workarounds but that wasn't really the point. Share this post Link to post
Dalija Prasnikar 1396 Posted December 8, 2022 5 hours ago, Anders Melander said: Casting a function result with the interface as operator and the old behavior is back. Reproduced with Delphi 11.2. I suppose you tested those outside the main code block. Have you filed QP report? Share this post Link to post
Anders Melander 1782 Posted December 8, 2022 8 minutes ago, Dalija Prasnikar said: I suppose you tested those outside the main code block. Actually, I didn't - until now. Same problem, I'm afraid. I haven't QP'd it and will probably not have time to do so anytime soon; We've just updated our main product suite to Delphi 11.2 (from 10.3) and there's plenty of other stuff that needs my attention. Here's the original test case updated to exhibit the observed behavior: program FooFailMore; {$APPTYPE CONSOLE} {$R *.res} uses Classes; type IFoo = interface ['{28036C58-4E0E-422C-AAE1-7DDEFF51C75D}'] procedure DoFoo; end; TFoo = class(TInterfacedObject, IFoo) private procedure DoFoo; public constructor Create; destructor Destroy; override; end; constructor TFoo.Create; begin inherited Create; WriteLn('Create'); end; destructor TFoo.Destroy; begin WriteLn('Destroy'); inherited; end; procedure TFoo.DoFoo; begin WriteLn('Foo'); end; function CreateFoo: IUnknown; begin Result := TFoo.Create; end; procedure Test; begin WriteLn('Begin'); begin var Foo1 := TFoo.Create as IFoo; Foo1.DoFoo; Foo1 := nil; // Foo1 destroyed here end; begin var Foo2 := CreateFoo as IFoo; Foo2.DoFoo; Foo2 := nil; end; WriteLn('End'); end; // Foo2 destroyed here begin Test; ReadLn; end. Expected output: Quote Begin Create Foo Destroy Create Foo Destroy End Actual output: Quote Begin Create Foo Destroy Create Foo End Destroy Share this post Link to post
Dalija Prasnikar 1396 Posted December 8, 2022 1 hour ago, Anders Melander said: I haven't QP'd it and will probably not have time to do so anytime soon; We've just updated our main product suite to Delphi 11.2 (from 10.3) and there's plenty of other stuff that needs my attention. I just looked at that code and the problem is not in the inline variable. Casting as interface creates hidden interface reference no matter what and that reference hinders release. This behavior is not a regression and it is not related to inline variables so I wouldn't hope it will be fixed soon. Reported as https://quality.embarcadero.com/browse/RSP-40166 1 2 Share this post Link to post