Recent

Author Topic: [solved] Unreleased DCs and unreleased GDIObjects - TImage memory leaks  (Read 989 times)

andyH

  • New Member
  • *
  • Posts: 23
Got an application which is closed with application.Terminate. When it closes I get the following error messages in the console:
Code: Diff  [Select]
  1. [TGtk2WidgetSet.Destroy] WARNING: There are 3 unreleased DCs, a detailed dump follows:
  2. [TGtk2WidgetSet.Destroy]  DCs:   00007FFFEE1C0E40 00007FFFEE1C0C40 00007FFFEE1C0A40
  3. [TGtk2WidgetSet.Destroy] WARNING: There are 6 unreleased GDIObjects, a detailed dump follows:
  4. [TGtk2WidgetSet.Destroy]   GDIOs: 00007FFFF7F6E2C0 00007FFFF7F6E240 00007FFFF7F6D7C0 00007FFFF7F6E140 00007FFFF7F6D6C0 00007FFFF7F6D740
  5. [TGtk2WidgetSet.Destroy]   gdiBitmap: 6
I thought the first three might be because I have some images/logos in the app and hadn't TImage.Free on completion, but this made no difference. As far as I'm aware all my Tobjects have .free when I've finished with them.

What are they and what am I doing wrong or not doing? A search on the web did not throw up anything useful.

Running Linux Mint 19.0 cinnamon and lazarus 2.0.2.
« Last Edit: June 11, 2019, 02:51:02 pm by andyH »

Soner

  • Full Member
  • ***
  • Posts: 152
Re: Unreleased DCs and unreleased GDIObjects
« Reply #1 on: June 09, 2019, 12:04:02 pm »
Did you create some objects manually and how? Like this:
MyObject:=TMyObject.Create(nil);
or like this:
MyObject:=TMyObject.Create(self);
(TMyObject can be TButton, TImage or something else)

When you created objects with nil as owner then they will be never freed. Because they don't exist in any list.
In LCL every object deletes it's child on deletion, application its forms and form its childs like button, panel...

howardpc

  • Hero Member
  • *****
  • Posts: 3144
Re: Unreleased DCs and unreleased GDIObjects
« Reply #2 on: June 09, 2019, 02:21:58 pm »
Got an application which is closed with application.Terminate. When it closes I get the following error messages
Without the code that produces the errors it is not possible to say how these errors arise, since they come not from Lazarus but from the gtk libraries, which don't usually have debugging information you can use to trace the call chain that raised such errors.
It could be to do with the parenting of your controls rather than their freeing, since it seems to be related to freeing of system resources you use, not access violation caused by incorrect destruction.

andyH

  • New Member
  • *
  • Posts: 23
Re: Unreleased DCs and unreleased GDIObjects
« Reply #3 on: June 09, 2019, 06:15:57 pm »
Yes, with the exception of the mainform, all forms and objects are created manually and some are MyObject:=TMyObject.Create(nil), an example being:

Code: Pascal  [Select]
  1. SaveDialog:= TSaveDialog.Create(nil);  

I did try SaveDialog:= TSaveDialog.Create(self) but I was getting compile errors - identifier not found "self". And when I'm finished with SaveDialog I have a SaveDialog.Free. From what you're saying this doesn't work, but I will check through my code to make sure every object has a corresponding .Free when finished.

Thanks for the pointer, I don't really understand .create(TheOwner : TComponent) and so far have been using self or nil where self doesn't work. Nor have I found anything useful on the web to improve my understanding.
« Last Edit: June 09, 2019, 06:18:18 pm by andyH »

Soner

  • Full Member
  • ***
  • Posts: 152
Re: Unreleased DCs and unreleased GDIObjects
« Reply #4 on: June 09, 2019, 06:40:16 pm »
When I can't use self in TSomething.Create(self) then I use the variable application or a object variable in same scope or some global object.
Or you can also use try..finally-blocks and free the objects self:

Code: Pascal  [Select]
  1.  SaveDialog:= TSaveDialog.Create(nil);
  2. try
  3.  //here you can access Savedialog
  4. finally
  5.  SaveDialog.Free;
  6. end;
  7.  

Sometimes I set objects at application start to nil and checks at end to nil:
Code: Pascal  [Select]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   FormAbout:=nil;
  4. end;
  5.  
  6. procedure TForm1.FormDestroy(Sender: TObject);
  7. begin
  8.   if Assigned(FormAbout) then FormAbout.Free;
  9. end;
  10.  

andyH

  • New Member
  • *
  • Posts: 23
Re: Unreleased DCs and unreleased GDIObjects
« Reply #5 on: June 09, 2019, 07:24:27 pm »
Thanks for that, I'll try both and see if I can whittle down the number of complaints from GTK. Particularly like the FormAbout:=nil;
« Last Edit: June 09, 2019, 07:27:02 pm by andyH »

howardpc

  • Hero Member
  • *****
  • Posts: 3144
Re: Unreleased DCs and unreleased GDIObjects
« Reply #6 on: June 09, 2019, 07:34:40 pm »
I don't really understand .create(TheOwner : TComponent) and so far have been using self or nil where self doesn't work. Nor have I found anything useful on the web to improve my understanding.
The concept of ownership is introduced in the declaration of TComponent which provides a public Owner property of type TComponent. A TComponent instance specified as Owner to other components implements a mechanism to ensure that all its owned components are freed before it is itself freed.

All components (that is all TComponent descendants) thus inherit an automatic freeing mechanism not available to non-component classes such as streams, stringlists etc. which lack any kind of ownership mechanism.

Provided you supply a non-nil Owner in the constructor when you instantiate your TComponent descendant you can then forget about manually freeing it. Its Owner will do that for you.

Of course this only works successfully if the Owner you specify has a longer life span than the to-be-automatically-freed component. So the Owner is usually a form, or a datamodule or the Application instance, which in most typical programs will outlive the GUI controls you want automatically freed.

You can ignore this automatic freeing mechanism by passing Nil as the Owner for a newly constructed component. That means you as programmer are taking full responsibility for freeing the component. If you forget to Free it, you will have a leak. The only "owner" of such components is you, the programmer, and if you forget what you own, you are in for trouble.
« Last Edit: June 09, 2019, 07:52:08 pm by howardpc »

andyH

  • New Member
  • *
  • Posts: 23
Re: Unreleased DCs and unreleased GDIObjects
« Reply #7 on: June 10, 2019, 01:22:45 pm »
Thank you for that, my understanding improves by increments, just wish there was a resource somewhere that wrote all this down  :(

andyH

  • New Member
  • *
  • Posts: 23
Re: Unreleased DCs and unreleased GDIObjects
« Reply #8 on: June 10, 2019, 04:51:57 pm »
Now I'm confused, I've removed all references to self or nil in all objects, they are now owned by either the mainform or a child of mainform, but am still getting the gtk errors on termination.

I've tracked the problem down to a confirmation form I put up to confirm the user's selections prior to doing the next action (a backup) - bypassing this form = no errors on exit.
The form is invoked from a file save dialog (to get the backup file name):
Code: Pascal  [Select]
  1.        if SaveDialog.Execute then
  2.           begin
  3.             BackFileName:= SaveDialog.Filename;
  4.             SaveDialog.Free;
  5.             ConfirmForm:= Tconfform.create(MainFm);
  6.             //set up labels. populate list boxes in the form
  7.             ...
  8.             ConfirmForm.Show;
  9.           end
There are two buttons on the confirm form, cancel and OK, the OK button onclick is:
Code: Pascal  [Select]
  1. procedure Tconfform.OkBtnCK(Sender : TObject);
  2. begin
  3.   writeln('Confirm - OK button clicked');
  4.   BkBackup;
  5. end;

And the first lines of BkBackup are (I don't know if you need both free and close, but I've tried all permutations):
Code: Pascal  [Select]
  1. begin
  2.   ConfirmForm.close;
  3.   ConfirmForm.free;
  4.   StartTime:= Time; //start time for partclone backup
  5.   ProgressFm:=TProgressFm.Create(MainFm.PageCtrl.Page[0]);
  6.   ProgressFm.Show;
  7.   and so on...
  8.  
I've also added an assign statement to the quit button on the mainform to exit the application:
Code: Pascal  [Select]
  1. begin
  2.   if Assigned(ConfirmForm) then ConfirmForm.Free;
  3.   application.Terminate;
  4. end;
Other than removing all reference to ConfirmForm and bypassing it, everything I've tried still throws up the GTK errors in my original post  :(

devEric69

  • Full Member
  • ***
  • Posts: 137
Re: Unreleased DCs and unreleased GDIObjects
« Reply #9 on: June 10, 2019, 06:12:44 pm »
I advise you to code like this:

1°) you must drop a button "&OK" on your TConfirmForm, and set its ModalResult=mrOK.

2°) in your main form, you code:

Code: Pascal  [Select]
  1. var
  2.   frmConfirmForm: TConfirmForm;
  3. begin
  4.   frmConfirmForm:= TConfirmForm.Create(Self);
  5.   try
  6.     frmConfirmForm.showmodal;
  7.     //you can test frmConfirmForm.modalResult ie which frmConfirmForm's btn has been clicked
  8.   finally
  9.     FreeAndNil(frmConfirmForm);
  10.   end;

3°) and in your callee modal dialog-box-form, you code this events:

Code: Pascal  [Select]
  1. procedure TConfirmForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  2. begin
  3.   CloseAction:= caFree;
  4. end;
  5.  
  6. procedure TConfirmForm.FormCloseQuery(Sender: TObject; var CanClose: TCloseAction);
  7. begin
  8.   CanClose:= True; //or a more complicated test
  9. end;

That way, you can be serene about possible memory leaks.
« Last Edit: June 10, 2019, 06:15:51 pm by devEric69 »
use: Ubuntu 18.04 + Laz. 1.8.5 + FPC 3.0.5 (64 bits).

andyH

  • New Member
  • *
  • Posts: 23
Re: Unreleased DCs and unreleased GDIObjects
« Reply #10 on: June 11, 2019, 02:45:52 pm »
Finally!!!! I fixed it, but not through any of the suggestions above - although they led me to trying every permutation and combination I could think off = two days work. The problem was the TImages on the form - these were not being freed when the form was freed.

I would like someone with more knowledge than me to explain why. I amended the code creating the form, based on the above to:
Code: Pascal  [Select]
  1.             try
  2.               ConfirmForm:= Tconfform.create(MainFm);
  3.               //populate the confirm form, first the source drive
  4.               ...more setup code for the form
  5.               ConfirmForm.ShowModal;
  6.               writeln('line before modalresult test........');
  7.               if ConfirmForm.modalResult = mrOK then writeln('show modalresult triggered.......');
  8.             finally
  9.               FreeAndNil(ConfirmForm);
  10.             end;
  11.  
No change from previous behaviour. I also tried changing Tconfform.create(MainFm) to Tconfform.create(Self) and got backup.pas(293,44) Error: Identifier not found "Self", why???
 
Changing the finally statement to:
Code: Pascal  [Select]
  1.             finally
  2.               ConfirmForm.cDriveImg.Free;
  3.               ConfirmForm.cFileImg.Free;
  4.               ConfirmForm.cArrowImg.Free;
  5.               FreeAndNil(ConfirmForm);
  6.             end;
Fixed the problem - no memory leaks on exit.

As an example, code for the constructor for ConfirmForm for one of the images is:
Code: Pascal  [Select]
  1.   cFileImg:=TImage.Create(ConfirmForm);
  2.   with cFileImg do
  3.        begin
  4.          Name:='File';
  5.          Parent:= Self;
  6.          left:= 20;
  7.          top:= 115;
  8.          autosize:= false;
  9.          Picture.LoadFromFile('/home/andy/backupGUIV7/file2-64.png');
  10.        end;
I did try omitting the parent statement, setting it to nil and setting it ConfirmForm, in each case it did not display on the form (same was true for every other object on the form)?

What was confusing me was that I've got other 'pop up' forms that were not causing any problems, I had free statements on them when finished = no memory leaks. The only difference - no TImages. So why are TImages causing a memory leak when the parent form is correctly freed??

As an 'on and off' pascal user since the mid '80's I think lazarus is brilliant, but I do find issues like this very frustrating - members of this forum have to give their time freely to fill the gaps, some of which could be avoided through decent documentation.

Finally, the form that has been causing me so much trouble, with the offending images    :)

« Last Edit: June 11, 2019, 02:48:48 pm by andyH »

lucamar

  • Hero Member
  • *****
  • Posts: 2012
It would probably have worked as it should if instead of:
Code: [Select]
  cFileImg:=TImage.Create(ConfirmForm);you had used
Code: [Select]
  cFileImg:=TImage.Create(Self);
You should never tie a control (or anything else, for that matter) to a specific object instance but to the current one, which is always accessible through Self.

That said, it's never a bad idea to free in the destructor whatever you create in the constructor, even if it's supposed to be freed automatically.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

devEric69

  • Full Member
  • ***
  • Posts: 137
lucamar is right.

Here's a little "topo", concerning memory management. There are few golden rules to which, we must adhere, if we want to manage memory well:
- the place where memory has been allocated (
Code: Pascal  [Select]
  1. new(pMyRecord);
or
Code: Pascal  [Select]
  1. oMyObject.create(TheOwnerIfAllowed)
), is the place where this memory must be released (
Code: Pascal  [Select]
  1. dispose(pMyRecord); ptr:= nil;
, or
Code: Pascal  [Select]
  1. FreeAndNil(oMyObject);
, or
Code: Pascal  [Select]
  1. oMyObject.Free; oMyObject:= nil
).
==> This means that the allocations in the heap that are generally made at the beginning of a procedure, must be made in the opposite order of the allocations, at the end of the same procedure: the code can be refactored by calling subroutines, but always in order to respect this golden rule!
- if sub-components are created by encapsulation in a proprietary component, what is created in the proprietary component's constructor must be destroyed in the opposite direction of their construction, in the proprietary component's destructor.

Taking into account what I just said, your images are contained in your dialog box: so, if you create them in your formCreate event, you must destroy them in the formDestroy event.

By sticking to these simple rules, and assigning nil to pointers whose memory has been released (use FreeAndNil wherever you can; otherwise use dispose or Free and then, think to assign just behind the pointer with nil!), this allows you to destroy testing with
Code: Pascal  [Select]
  1. if Assigned(oMyObject) then FreeAndNil(oMyObject);

Then, to go further in the understanding of Free Pascal, there are two important notions whose documentation must be read:
- the owner (which is either the first AOwner parameter passed to a constructor), or a TForm or Tframe on which a control is placed, by drop from the pallet. The owner has the responsibility to destroy everything he owns: it avoids calls to FreeAndNil. That said, if you are a little paranoid, the rules explained above should be able to continue making "safe" paranoid calls to FreeAnNil in the places I've indicated (which are bijective in places of their explicit creation).
- the parent: the parent of a visual control is simply the visual component on which your visual component is  placed on. A control (ie a visual component) can therefore jump visually from one control to another, if you change programatically its parent property (for example, it can jump from a container like TPanel towards another like TGroupBox). That's all: it has nothing to do with memory management.

I'm going to say it again: you really have to do everything, but really do everything - otherwise, it will come back to you "in your teeth" sooner or later - to set your pointers with nil after having misallocated the memory on which they pointed, because internally, their Free and Destroy methods also use a test similar like:
Code: Pascal  [Select]
  1. if (pMyRecord <> nil) then "release"

If the pointer still has an address in the heap but its content has been released (but it's not constant: it depends on what runs in addition with Laz. on your PC, the memory previously allocated can be partially reset with other things), then its destruction will result in a "memory access violation" at 99% .

>:D ==> by not assigning with nil your "unallocated" pointers, you will go straight towards fuck*** dangling pointers problems, with "memory access violation".

(one last thing: there is also the management of interface memory, which is a completely different world. I say that, in case you are asked to use them to create \ use a COM server, or to do behavioral polymorphism's logic, instead of inheritance polymorphism's logic, and if you are prevented from using Helper for TMyClass that allow you to stay in inheritance polymophism's logic).
« Last Edit: June 11, 2019, 05:48:35 pm by devEric69 »
use: Ubuntu 18.04 + Laz. 1.8.5 + FPC 3.0.5 (64 bits).

andyH

  • New Member
  • *
  • Posts: 23
Each day I learn a little more, thanks for the detailed explanations. In future I will use freeandnil instead of tobject.free. Also thought I'd tried cFileImg:=TImage.Create(Self) as I stumbled towards a solution. Obviously not, as I tried it and it worked. %)

RAW

  • Hero Member
  • *****
  • Posts: 794
Quote
...just wish there was a resource somewhere that wrote all this down  :(
Try this: https://castle-engine.io/modern_pascal_introduction.html
Or if you like pdf's then this: https://castle-engine.io/modern_pascal_introduction.pdf

 :)
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1