Incus J 10 Posted March 28, 2023 (edited) I'm reading up on interfaces, and trying to understand when a reference counted object will be freed. var MyObject:IMyInterface; begin if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin //Do something with MyObject end; end; //MyObject is freed automatically here? If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns - or will it have fallen out of scope? How about if I omit 'var' from the parameter? Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow? Edited March 28, 2023 by Incus J Share this post Link to post
Anders Melander 1783 Posted March 28, 2023 18 minutes ago, Incus J said: If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns Yes. 18 minutes ago, Incus J said: How about if I omit 'var' from the parameter? Then there are no references to the object and it will be destroyed right before CreateAndReturnAnObject returns. IMO it would be better if you declare CreateAndReturnAnObject a function that returns an interface. 20 minutes ago, Incus J said: Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented... MyObject is not an object; It's a reference (i.e. a pointer) to an object. 22 minutes ago, Incus J said: I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow? First of all, it's important to know that a reference-counted object is destroyed when the reference count is decreased to 0. Now, when the object has been created it has a reference count of 0. When you assign the object to a variable (e.g. Result or "var MyObject") you create a reference and the ref count is increased to 1. The MyObject value returned from CreateAndReturnAnObject maintains the reference so the ref count isn't decremented. Only when MyObject goes out of scope, at the final "end;" will MyObject be nilled which decrements the ref count so it reached 0 and the object is destroyed. If your object is based on TInterfacedObject then I suggest you place breakpoints in all of the methods of TInterfacedObject and watch what goes on. For instance, it's good to know why the object starts with a ref count of -1 and how Delphi makes that work out in the end. There are some strange corner cases but basic reference counting in Delphi really is very simple and logical once you understand the rules. 2 Share this post Link to post
Incus J 10 Posted March 28, 2023 Thank you - that's a good clear explanation. I'll have a go at your suggestion and step through with the debugger to see what actually happens. Quote IMO it would be better if you declare CreateAndReturnAnObject a function that returns an interface. That does make sense, though I like to have a boolean return value to test success against in a compact way: if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin vs MyObject := TSomeclass.CreateAndReturnAnObject; if Assigned(MyObject) then begin ...but perhaps there's a better approach? Share this post Link to post
Remy Lebeau 1394 Posted March 28, 2023 2 hours ago, Incus J said: If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns - or will it have fallen out of scope? It depends. You did not show the actual declaration and implementation of CreateAndReturnAnObject(), so it is possible that it could go either way. For instance, if the parameter is an untyped var, then the created object WILL NOT have its reference count incremented when assigned to the parameter, and so the object will die (or worse, be leaked) when CreateAndReturnAnObject() exits: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin MyObject := TMyInterface.Create; // <-- refcount IS NOT incremented! Result := True; end; To solve that, you would need to use an explicit type-cast: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin IMyInterface(MyObject) := TMyInterface.Create; // <-- refcount IS incremented! Result := True; end; // Or: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin MyObject := TMyInterface.Create as IMyInterface; // <-- refcount IS incremented! Result := True; end; Or, call _AddRef() directly: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; var Intf: IMyInterface; begin Intf := TMyInterface.Create; // <-- refcount IS incremented here MyObject := Intf; Intf._AddRef; // <-- refcount is incremented again Result := True; end; // <-- refcount is decremented here On the other hand, if the parameter is typed as a var reference to IMyInterface, then the reference count WILL be incremented as expected: function TSomeclass.CreateAndReturnAnObject(var MyObject: IMyInterface): Boolean; begin MyObject := TMyInterface.Create; // <-- refcount IS incremented! Result := True; end; The calling code you have shown would compile in both cases (once you drop the var at the call site, see below), so it is important to know what the parameter is actually typed as to begin with. 2 hours ago, Incus J said: How about if I omit 'var' from the parameter? Specifying var when passing a variable to a function parameter is not valid syntax to begin with. You can use var only when declaring a variable/parameter, but not when passing it around. Pascal is not C#, or other languages, that require you to be explicit about how a variable is passed to a function parameter. 2 hours ago, Incus J said: Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow? It WILL be auto-incremented when it is assigned to a variable/parameter of a valid interface type. 1 Share this post Link to post
Incus J 10 Posted March 28, 2023 (edited) So much to learn - and so many opportunities to make mistakes that will still compile while unwittingly shooting myself in the foot 🙂 I didn't even know an untyped var parameter was permitted - in my specific case it would be typed, as below, but I can see myself doing something like accidentally specifying a type of TMyInterface instead of IMyInterface due to a momentary distraction, tiredness or loss of focus. Quote Specifying var when passing a variable to a function parameter is not valid syntax Yes, sorry - that's a typo on my part, should have been: if SomeclassInstance.CreateAndReturnAnObject(MyObject) then begin My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return, whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed: function TSomeclass.CreateAndReturnAnObject(var MyObject:IMyInterface):boolean; begin MyObject:=TMyInterface.Create; result:=true; end; //MyObject retained. vs function TSomeclass.CreateAndReturnAnObject(MyObject:IMyInterface):boolean; begin MyObject:=TMyInterface.Create; result:=true; end; //MyObject vapourises here. When an interfaced object (e.g. MyObject) goes out of scope and is automatically freed, does it get nil assigned to it, or does it behave the same as non-interfaced objects which keep a dangling pointer to the freed instance after SomeObject.Free (unless SomeObject.FreeAndNil is used to free it)? Edited March 28, 2023 by Incus J Share this post Link to post
Anders Melander 1783 Posted March 28, 2023 5 hours ago, Incus J said: perhaps there's a better approach? Yes: Don't be lazy. This is the better approach: 5 hours ago, Incus J said: MyObject := TSomeclass.CreateAndReturnAnObject; if (MyObject <> nil) then begin ... Don't use Assigned() unless you have a reason to. Assigned() just adds another layer that your brain has to parse. Testing against nil makes it immediately clear what's going on. 51 minutes ago, Incus J said: My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return Yes. 52 minutes ago, Incus J said: whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed: Yes and no. Since the MyObject parameter is a value parameter you're not returning anything (besides the boolean). And since you're not returning a reference to the interface the ref count will become 0 when the method returns and the object will be destroyed. The debugger would have shown you this if you'd tried it. 1 Share this post Link to post
Remy Lebeau 1394 Posted March 29, 2023 4 hours ago, Incus J said: I didn't even know an untyped var parameter was permitted https://docwiki.embarcadero.com/RADStudio/en/Parameters_(Delphi)#Untyped_Parameters 4 hours ago, Incus J said: My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return Yes. 4 hours ago, Incus J said: whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed No. It would not return anything at all, because the parameter is being passed in by value, not by var reference. So, it is essentially just a local variable that is a copy of the caller's variable. As such, you are just assigning the object to a local variable, and the caller's variable is left untouched. The object's refcount will be incremented when the local variable is assigned to, and then be decremented when the local variable goes out of scope. The caller will never see the object at all. 4 hours ago, Incus J said: When an interfaced object (e.g. MyObject) goes out of scope and is automatically freed, does it get nil assigned to it It is not the object itself that goes out of scope, just the interface variable that refers to the object. But no, the variable is not assigned nil, it is already out of scope, its value doesn't matter anymore. But, when the variable does go out of scope, if it has a non-nil value then the refcount of the object it is referring to will be decremented. The object is automatically destroyed only when its refcount falls to 0. 4 hours ago, Incus J said: or does it behave the same as non-interfaced objects which keep a dangling pointer to the freed instance after SomeObject.Free (unless SomeObject.FreeAndNil is used to free it)? Never call Free() on an interfaced object. Use reference counting instead. And, there is no FreeAndNil() member method, only a standalone function. So SomeObject.FreeAndNil() is not valid, only FreeAndNil(SomeObject) is valid. And, it is only valid on an object variable, not an interface variable. In any case, if you need an interface variable, that has not gone out of scope yet, to be set to nil automatically when the object it refers to is destroyed, you need to declare the interface variable as [weak]. Share this post Link to post
programmerdelphi2k 237 Posted March 29, 2023 (edited) as you want "have" a obj-reference in your function( var XXXX ):boolean.... you should use function( out XXXX):boolean; the scope should be the same then "var definition" on caller procedure!!! if "the var is LOCAL (into proceudre) then the life is local", else if the var is "unit-GLOBAL", then, the obj is alive while unit is alive. unit uMyInterfX; interface type IMyInterfX = interface ['{AE871A32-4B6A-4E4B-B825-92B762493D3F}'] function Hello(AValue: string = ''): string; function HelloObj(out MyObjOUT: IMyInterfX): boolean; end; TMyClassX = class(TInterfacedObject, IMyInterfX) private public function Hello(AValue: string = ''): string; function HelloObj(out MyObjOUT: IMyInterfX): boolean; end; implementation uses System.SysUtils; { TMyClassX } function TMyClassX.Hello(AValue: string = ''): string; begin result := 'Hello World: ' + AValue + ' ref-Counting: (' + Self.RefCount.ToString + ') '; end; function TMyClassX.HelloObj(out MyObjOUT: IMyInterfX): boolean; begin result := false; // try MyObjOUT := TMyClassX.Create; // created locally, but will be refereced in "caller Obj"-scope!! result := true; except // ? end; end; end. var Form1: TForm1; implementation {$R *.dfm} uses uMyInterfX; var LMyInterfXGLOBALForUnit: IMyInterfX; LObjGlobal : IMyInterfX; procedure MyOtherProc(const AObj: IMyInterfX); begin if (AObj <> nil) then ShowMessage(AObj.Hello(' AObj from MyOtherProc')); end; procedure TForm1.Button1Click(Sender: TObject); var LMyInterfXLOCALForProcedure: IMyInterfX; LObj : IMyInterfX; // local obj interfaced but will created into "TMyClass interfaced" ... it is gone too! LObj2 : IMyInterfX; begin LMyInterfXLOCALForProcedure := TMyClassX.Create; // ...try ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXLOCALForUnit'); // finally // ...LMyInterfXLOCALForProcedure.FREE; // does not works in Interfaced object!!! // ...end; // if LMyInterfXLOCALForProcedure.HelloObj(LObj) and (LObj <> nil) then ShowMessage(LObj.Hello(' from LObj (Local)')); // MyOtherProc(LObj); LObj2 := LObj; MyOtherProc(LObj); // // LMyInterfXGLOBALForUnit := TMyClassX.Create; // ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXGLOBALForUnit'); // if LMyInterfXGLOBALForUnit.HelloObj(LObjGlobal) and (LObjGlobal <> nil) then ShowMessage(LObjGlobal.Hello(' from LObj (Global)')); // // summary: // LMyInterfXLOCALForProcedure is done when this procedure out-of-scope // LMyInterfXGLOBALForUnit still alive same that this procedure is out-of-scope... until unit is gone! end; procedure TForm1.Button2Click(Sender: TObject); var LObj2: IMyInterfX; begin // ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXLOCALForUnit'); // not works because it's a LOCAL definition // if (LMyInterfXGLOBALForUnit <> nil) then begin ShowMessage(LMyInterfXGLOBALForUnit.Hello + ' LMyInterfXGLOBALForUnit is alive'); // if (LObjGlobal <> nil) then ShowMessage(LObjGlobal.Hello(' Obj Global its alive ')) else ShowMessage('Obj Global is nil'); end else ShowMessage('LMyInterfXGLOBALForUnit is nil'); // MyOtherProc(LObjGlobal); LObj2 := LObjGlobal; MyOtherProc(LObjGlobal); end; initialization ReportMemoryLeaksOnShutdown := true; end. Edited March 29, 2023 by programmerdelphi2k 1 Share this post Link to post
programmerdelphi2k 237 Posted March 29, 2023 (edited) implementation uses System.SysUtils, Vcl.Dialogs; { TMyClassX } destructor TMyClassX.Destroy; begin ShowMessage('calling TMyClass.Destroy...'); /// how many times is called? // inherited; end; // verifying pointer function TMyClassX.Hello(AValue: string = ''): string; begin result := 'Pointer: ' + integer(Self).ToString + ' ' + 'Hello World: ' + AValue + ' ref-Counting: (' + Self.RefCount.ToString + ') '; end; Edited March 29, 2023 by programmerdelphi2k Share this post Link to post
Incus J 10 Posted March 31, 2023 Quote 4 hours ago, Incus J said: whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed No. It would not return anything at all, because the parameter is being passed in by value, not by var reference. Ah - so that is a key difference to, for example, passing in an parameter of type TStringlist - even without 'var' I think an object such as TStringlist gets passed by reference (?) Share this post Link to post
Remy Lebeau 1394 Posted March 31, 2023 12 minutes ago, Incus J said: Ah - so that is a key difference to, for example, passing in an parameter of type TStringlist - even without 'var' I think an object such as TStringlist gets passed by reference (?) Yes, because objects are reference types. You are passing in a pointer to a TStringList object, so it doesn't matter whether the pointer itself is passed by value or by reference, you still have to dereference the pointer either way to access the object. Share this post Link to post