Recent

Author Topic: [SOLVED] Access violation when ending program / Forms with Parent 'Application'  (Read 7542 times)

alpine

  • Hero Member
  • *****
  • Posts: 1410

This AV occured only once in more than 1 year. Plenty of new runs did not occur it again. I would have to deactivate the inipropstorage mechanism probably for a very long time. And as long as the AV doesn't occur again, I would never know, if the AV was caused by the inipropstorage writing mechanism or not, so I could never repair it.
I think it's better, to let it be active, but with the mentioned debug output when it has completed. If the inipropstorage writing mechanism would ever cause an AV, then I could identify this surely.
A single occurrence may mean nothing, it may be a single-event error, ie. a bitflip.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Hello Thaddy,
thanks that you want to help me, but - as often - you do not help me really, because you give no real answers to my questions:
But I think I did. You can get at all the component properties, like name, classname, parent, whatever.
Sorry, I think you have not. This time you explained, what type of properties a component can have. But this - once more - was not the question.
I don't want to argue with you. I've read that your health is not well. I wish you the very best.

Maybe someone else can answer this questions?
 - Which components are at any one time contained in array Application.Components[]?
 - Which is the rule, whether and when a Component is added to this array?
 - And which is the rule, whether and when a Component is deleted from this array?

wp

  • Hero Member
  • *****
  • Posts: 13200
The basic purpose of the Components list of a component C is to store all the components which were created with C as owner; and it is needed to destroy all these components when C itself is destroyed.

Application essentially is a descendant of TComponent and thus has a Components list. When in your project unit "Application.CreateForm(TForm1, Form1)" is called, an instance of TForm1 is created, and it has Application as parent, i.e. is held in Application.Components. Or the same happens when you, in your own code, for example after a button click, create another form by "Form2 := TForm2.Create(Application)".

When you destroy a form (or more general: a component owned by Application) it instructs Application to remove itself from the Application.Components list so that Application will not attempt to do this when the app terminates.


Thaddy

  • Hero Member
  • *****
  • Posts: 18306
  • Here stood a man who saw the Elbe and jumped it.
Maybe someone else can answer this questions?
 - Which components are at any one time contained in array Application.Components[]?
 
Which EXACTLY what I showed. Which component.... You did not even try my example.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
A single occurrence may mean nothing, it may be a single-event error, ie. a bitflip.
During many years I have found too many bugs in my sources to lean backwards with my AV on that ;-)



The basic purpose of the Components list of a component C is to store all the components which were created with C as owner; and it is needed to destroy all these components when C itself is destroyed.

Application essentially is a descendant of TComponent and thus has a Components list. When in your project unit "Application.CreateForm(TForm1, Form1)" is called, an instance of TForm1 is created, and it has Application as parent, i.e. is held in Application.Components. Or the same happens when you, in your own code, for example after a button click, create another form by "Form2 := TForm2.Create(Application)".

When you destroy a form (or more general: a component owned by Application) it instructs Application to remove itself from the Application.Components list so that Application will not attempt to do this when the app terminates.
Many thanks wp for this informations. It helps a lot for a 1st understanding.

In reply #5 I showed this code:
Code: Pascal  [Select][+][-]
  1.  procedure show_Application_Components;
  2.    {displays the names of all Application.Components[]}
  3.  
  4.    function get_Component_Name(p: TObject): ansistring;
  5.       begin
  6.       if p=nil then exit('nil');
  7.       if p is TComponent then exit(TComponent(p).Name + ':' + p.ClassName);
  8.       exit(p.ClassName);
  9.       end;
  10.  
  11.    var i: integer;
  12.    begin
  13.    writeln('show_Application_Components => ');
  14.    for i:=Application.ComponentCount-1 downto 0 do
  15.       writeln(i:2, ' ', get_Component_Name(Application.Components[i]));
  16.    end;
I call this procedure in my main source file, directly after line Application.Run(), which is the very last line of this file. An example of the result is:
Code: Text  [Select][+][-]
  1. show_Application_Components =>
  2. 15) :TFormMM
  3. 14) :TFormMM
  4. 13) :TFormZ
  5. 12) :TFormMM
  6. 11) :TFormZ
  7. 10) :TFormMM
  8.  9) :TFormMM
  9.  8) :TFormZ
  10.  7) :TFormMM
  11.  6) :TFormR
  12.  5) :THintWindow
  13.  4) :TFormMM
  14.  3) :TFormMM
  15.  2) :TFormR
  16.  1) :TCustomTimer
  17.  0) Form1:TForm1

We see there 'Form1:TForm1' at Index 0, so it was the 1st created component by calling Application.CreateForm(TForm1,Form1) in the main source file, as you explained.

Instead of "FormZ:=TFormZ.Create(Application)" I always use this style: "FormZ:=TFormZ.CreateNew(Application)" and my program uses about a dozen of them. Somewhere I've read, that CreateNew() should be used instead of Create(), "if a Form has no resources". I'm not sure, what "resources" here means exactly, but I always thought, it means "if a Form has no *.lfm file".
Question: is that correct?

'TCustomTimer' I don't know, must be used by the LCL I assume.
'THintWindow' is used by me and created by PN:=TPopupNotifier.Create(Application).

The rest are Forms, which are used non modal. They all are created via style "FormX:=TFormX.CreateNew(Application)" because, if the program terminates and a non modal form is still open, it is closed automatically. To close such a non modal form via a button or by code I call self.Close(), but (currently) not self.Free(). Obviously this leads to having such a Form multiple times in Application.Components[], if such a Form is used multiple times.
Question: is this technique correct or could it lead to the AV, which I had in procedure TApplication.DoBeforeFinalization() - I showed and explained it in reply #5 - or to other problems?

I never call Destroy() in my sources. I assume, that Destroy() is called automatically, if I call Free().
Question: is this true?

wp

  • Hero Member
  • *****
  • Posts: 13200
Instead of "FormZ:=TFormZ.Create(Application)" I always use this style: "FormZ:=TFormZ.CreateNew(Application)" and my program uses about a dozen of them. Somewhere I've read, that CreateNew() should be used instead of Create(), "if a Form has no resources". I'm not sure, what "resources" here means exactly, but I always thought, it means "if a Form has no *.lfm file".
Question: is that correct?
yes

The rest are Forms, which are used non modal. They all are created via style "FormX:=TFormX.CreateNew(Application)" because, if the program terminates and a non modal form is still open, it is closed automatically. To close such a non modal form via a button or by code I call self.Close(), but (currently) not self.Free(). Obviously this leads to having such a Form multiple times in Application.Components[], if such a Form is used multiple times.
Question: is this technique correct or could it lead to the AV, which I had in procedure TApplication.DoBeforeFinalization() - I showed and explained it in reply #5 - or to other problems?
I would not do this (i.e. always creating new forms, just "closing" without freeing them) . Because you accept some kind of memory leak during runtime. When your application runs for a long time it may eventually run out of memory.

When you call Form.Close you do not destroy the form by default, you just hide it because you might want to reshow it in its current state later. If you want to destroy it when closing you must provide a handler for the OnClose event and set the CloseAction argument to caFree and the form will destroy itself. Do not Free the form yourself in this case. BUT: when you have a reference variable for the form somewhere you must always make sure that this variable is set to nil. This may be complicated if you allow several instances of the same form class! Modal forms are much easier...

I never call Destroy() in my sources. I assume, that Destroy() is called automatically, if I call Free().
Question: is this true?
Yes

egsuh

  • Hero Member
  • *****
  • Posts: 1694
Quote
My program always writes some debug infos into a terminal. The last 2 lines before the AV were:

    TForm1.FormClose() finished
    TForm1.IniPropStorage1SavingProperties() started
    [FORMS.PP] ExceptionOccurred
    ...

That means: event FormClose() of the main form had run completely and the event to write the INI-file was started correctly. There is currently no debug message when this event has run completely, but the INI-file was written correctly and had a current timestamp. So the AV must have occured "very late".

Have you checked whether TForm1.FormClose destroys itself?  For example, somehow caFree is assigned at FormClose event. (BTW what is the default value of CloseAction?)

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  2. begin
  3.      CloseAction := caFree;
  4. end;
  5.  


And following is strange

         Form2 := TForm2.Create(Application);

because in the project file forms are created in following way

       Application.CreateForm(TForm2, Form2);

I simply have been thinking that Application treats its OWNED components in somewhat different way than ordinary components.  No difference? I do not want to dig into any further.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Thanks a lot wp for clarifications.

The rest are Forms, which are used non modal. They all are created via style "FormX:=TFormX.CreateNew(Application)" because, if the program terminates and a non modal form is still open, it is closed automatically. To close such a non modal form via a button or by code I call self.Close(), but (currently) not self.Free(). Obviously this leads to having such a Form multiple times in Application.Components[], if such a Form is used multiple times.
Question: is this technique correct or could it lead to the AV, which I had in procedure TApplication.DoBeforeFinalization() - I showed and explained it in reply #5 - or to other problems?
I would not do this (i.e. always creating new forms, just "closing" without freeing them) . Because you accept some kind of memory leak during runtime. When your application runs for a long time it may eventually run out of memory.

When you call Form.Close you do not destroy the form by default, you just hide it because you might want to reshow it in its current state later. If you want to destroy it when closing you must provide a handler for the OnClose event and set the CloseAction argument to caFree and the form will destroy itself. Do not Free the form yourself in this case. BUT: when you have a reference variable for the form somewhere you must always make sure that this variable is set to nil. This may be complicated if you allow several instances of the same form class! Modal forms are much easier...
If I understood you correctly, then I must free such a non modal form after calling self.Close() to avoid memory leaks and I have 2 alternatives to do that:
a) either I setup an Event handler for the OnClose event and inside this event I set 'CloseAction:=caFree'. Then this Form will destroy itself automatically, after the call to Form.Close has finished.
b) or I call self.Free() manually directly after each self.Close().
If an Event handler for the OnClose event already exists or if I have many calls to self.Close() in my program, than a) would be less work. Otherwise b) would be less work. But both alternatives are save to use.
Question: Is this correct?

Quote
BUT: when you have a reference variable for the form somewhere you must always make sure that this variable is set to nil. This may be complicated if you allow several instances of the same form class! Modal forms are much easier...
Sorry, I don't understand this. What do you mean with 'reference variable' here?



Thanks egsuh for your reply.

Have you checked whether TForm1.FormClose destroys itself?  For example, somehow caFree is assigned at FormClose event. (BTW what is the default value of CloseAction?)
I already had an event handler TForm1.FormClose(). Inside I did never change var 'CloseAction'. I printed it and it contains 'caFree'. So from my understanding this is correct.

Quote
And following is strange
         Form2 := TForm2.Create(Application);
because in the project file forms are created in following way
       Application.CreateForm(TForm2, Form2);
"Form2 := TForm2.Create(Application)" was only an example showed by wp.
As I wrote in reply #34, I nowhere use this style and in the main source file I call Application.CreateForm(TForm1,Form1).

wp

  • Hero Member
  • *****
  • Posts: 13200
If I understood you correctly, then I must free such a non modal form after calling self.Close() to avoid memory leaks and I have 2 alternatives to do that:
a) either I setup an Event handler for the OnClose event and inside this event I set 'CloseAction:=caFree'. Then this Form will destroy itself automatically, after the call to Form.Close has finished.
b) or I call self.Free() manually directly after each self.Close().
If an Event handler for the OnClose event already exists or if I have many calls to self.Close() in my program, than a) would be less work. Otherwise b) would be less work. But both alternatives are save to use.
Question: Is this correct?
Hmm...

Option (b) has the risk that the user closes the form by means of the "x" button in the corner. In this case your explicit call to Form.Free is not executed. The form, by default, is only hidden (CloseAction = caHide by default; see below), and you have the memory leak mentioned in my previous post. (Note that this memory leak is not detected by HeapTrc because the closed form still exists in the Application.Components and thus is destroyed by the application at the end).

Therefore you should provide an OnClose handler which sets CloseAction to caFree. But now you have the problem that the variable which refers to the destroyed form is not set to nil.

Let me explain it by an example:

Suppose you have a MainForm (class TMainForm) with a button to open a SettingsForm (class TSettingsForm) (which is not auto-created, but created manually upon the button-click):
Code: Pascal  [Select][+][-]
  1. procedure TMainForm.Button1Click(Sender: TObject);
  2. begin
  3.   if SettingsForm = nil then  // avoid creation of multiple instances of the SettingsForm
  4.     SettingsForm := TSettingsForm.Create(self);  // or: Application.CreateForm(TSettingsForm, SettingsForm);
  5.   SettingsForm.Show;
  6. end;

There is another button to close the SettingsForm:
Code: Pascal  [Select][+][-]
  1. procedure TMainForm.Button2Click(Sender: TObject);
  2. begin
  3.   SettingsForm.Free;
  4.   SettingsForm := nil;
  5. end;

When the user clicks on Button1 the Settingsform opens, and when he clicks on Button2 the SettingsForm closes. And when he clicks on Button1 again, the same repeats. - Fine.

But when he closes the SettingsForm by means of the "x" button, the code in Button2Click is not executed, and the variable "SettingsFrame" is not reset to nil. Therefore, when you open the SettingsForm a second time the application will crash in "SettingsForm.Show" of Button1Click because the Settingsform was not recreated (because it is not nil) and thus does not exist any more in SettingsForm.Show.

Therefore, the SettingsForm needs an OnClose handler:
Code: Pascal  [Select][+][-]
  1. procedure TSettingsForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  2. begin
  3.   CloseAction := caFree;
  4.   SettingsForm := nil;
  5. end;
  6.  

This works. But what if the instance of TSettingsForm is not the global variable "SettingsForm" as proposed by the IDE? How can TSettingsForm know the name it was given by the mainform? It can't...

Therefore, I would not implement the OnClose handler of TSettingsForm as a method of TSettingsForm itself, but as a method of TMainForm which knows about the name of the instance:
Code: Pascal  [Select][+][-]
  1. procedure TMainForm.SettingsFormClose(Sender: TObject; var CloseAction: TCloseAction);   // NOTE: This is a method of TMainForm!
  2. begin
  3.   if Sender = SettingsForm then
  4.   begin
  5.     CloseAction := caFree;
  6.     SettingsForm := nil;
  7.   end;
  8. end;

The event handler must be assigned when the SettingsForm is created, i.e. in Button1Click:
Code: Pascal  [Select][+][-]
  1. procedure TMainForm.Button1Click(Sender: TObject);
  2. begin
  3.   if SettingsForm = nil then  // avoid creation of multiple instances of the SettingsForm
  4.     SettingsForm := TSettingsForm.Create(self);  // or: Application.CreateForm(TSettingsForm, SettingsForm);
  5.   SettingsForm.OnClose := @SettingsFormClose;
  6.   SettingsForm.Show;
  7. end;

I am attaching a mini-project bases on the final solution (I renamed SettingsForm to FSettingsForm here to avoid confusion with the auto-generated form).

What do you mean with 'reference variable' here?
In above sample, this is the variable "SettingsForm".

I already had an event handler TForm1.FormClose(). Inside I did never change var 'CloseAction'. I printed it and it contains 'caFree'.
This is strange. Look at the sources of TCustomForm.Close: CloseAction is caFree only for the MainForm.
Code: Pascal  [Select][+][-]
  1. procedure TCustomForm.Close;
  2. var
  3.   CloseAction: TCloseAction;
  4.   IsMainForm: Boolean;
  5. begin
  6.   if fsModal in FFormState then
  7.     ModalResult := mrCancel
  8.   else
  9.   begin
  10.     if CloseQuery then
  11.     begin
  12.       // IsMainForm flag set if we are closing MainForm or its parent
  13.       IsMainForm := (Application.MainForm = Self) or (Self.IsParentOf(Application.MainForm));
  14.       // Prepare default close action
  15.       if FormStyle = fsMDIChild then
  16.       begin
  17.         CloseAction := caNone;
  18.         // TODO: mdi logic
  19.       end
  20.       else
  21.       begin
  22.         if IsMainForm then
  23.           CloseAction := caFree
  24.         else
  25.           CloseAction := caHide;  
  26.   [...]
  27.  
« Last Edit: September 04, 2025, 12:59:55 pm by wp »

alpine

  • Hero Member
  • *****
  • Posts: 1410
A single occurrence may mean nothing, it may be a single-event error, ie. a bitflip.
During many years I have found too many bugs in my sources to lean backwards with my AV on that ;-)
You're right of course. But since it is just one occurrence and it may never manifest again, I doubt the effort you're about to put chasing it.
Just to note two evincible cases that I have experienced:
  • Couple of decades ago, defective RAM made my computer to behave strangely, incl. erratic AVs, until I find out what the reason is, my NTFS logical disks were rendered like cabbage. Holes gradually formed from the bitflips throughout the whole logical disk structure.
  • Very recently, we returned several Core i9 processors as a complaint because they became defective over time. Similar behavior.
I think it could be a very likely cause. IMHO efforts should be directed towards reproducing the error.

About the discussion for the forms creation/destruction, the thing is that LCL usually manages to free them correctly (as long as they have a valid Owner given). Using the form.Free is also foreseen, since the form must process some finalizing messages from the widgetset before being released. The trouble usually arises when they're destroyed not in order that you might expect and some shared objects used after being freed, eg. into OnDestroy you're trying to change the label caption elsewhere.

Overall, stopping procedure should be considered very carefully. For instance using FreeAndNil and/or raising global `ShutdownInProgress` flag could be of help here.
« Last Edit: September 04, 2025, 04:01:30 pm by alpine »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

440bx

  • Hero Member
  • *****
  • Posts: 5805
I have to concur with all who consider chasing a single AV that cannot be reproduced after a significant number of tries to be of uncertain value.

It is possible that there was one bit that had the wrong value, this very rarely happens "for no reason" but, it does happen.  (another way we've gone backwards... a lot of today's PC systems don't support ECC memory, the 440bx chipset did ;) )

Along the lines of what @alpine mentioned, long ago, I found out the hard way that the best way to test memory is to create a fairly large archive, e.g, 600MB or more, then tell the archiver to test the archive about 6 times in a row.  If the memory is good, it will pass every time.  If there is a memory problem, it will fail one or more of the tests.  The manufacturer confirmed the memory stick was defective after testing it using their equipment.

For those who think, use memtest or some other piece of software dedicated to memory testing.  Those utilities are the reason I mentioned I learned this the hard way.  I once had, a stick of memory that passed every tests multiple utilities threw at it for over 24 hours of continuous testing but, it was not able to pass the archive test.   I have to admit, it is very rare for something like that to happen but, I know it does because it happened to me, one time.

Maybe the AV occurred due to some memory fluke. Just in case, I'd do that archive test to ensure there isn't a memory stick that is "weak".  Additionally, the nice thing about that way of testing memory is that, the system is still available to do other things unlike some dedicated memory tests that have exclusive use of the machine while they are testing.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Thank you very much wp for your explanations and examples and the demo. Again I learned some more things I did not know before or could only assume, but was not sure.

Option (b) has the risk that the user closes the form by means of the "x" button in the corner. In this case your explicit call to Form.Free is not executed. The form, by default, is only hidden (CloseAction = caHide by default; see below), and you have the memory leak mentioned in my previous post. (Note that this memory leak is not detected by HeapTrc because the closed form still exists in the Application.Components and thus is destroyed by the application at the end).

Therefore you should provide an OnClose handler which sets CloseAction to caFree.
This is very important to know. Meanwhile I found, that some of my non modal Forms do have an Event handler for 'OnClose', which sets 'CloseAction:=caFree', but not all. I will change that.

Quote
But now you have the problem that the variable which refers to the destroyed form is not set to nil.
When I created my first non modal Forms, I had the same problem: I needed some kind of variable to know, whether a non modal Form currently is opened or not. Because if e.g. the user presses a button to open the Form, although it is already open, it would be wrong to create it again. Instead self.BringToFront should be called.
 - I found no solution by using a boolean property inside the Form, because this is lost, after the Form is free'd.
 - And I did not manage this by setting the Form variable itself to 'nil' after closing it. Because it is possible to close a Form by the "X" in the upper right corner, it would have been done in the OnDestroy Event handler, but a 'self:=nil' does not work there.
My solution was to use 1 separate exclusive global boolean variable for each non modal Form and to reset it in the OnClose Event handler. Works of course only, if this Class has never more than 1 instance at the same time.
For those Form classes, where more than 1 instance at the same time are needed, I added a pointer to an exclusive global boolean variable to the class properties and in the OnClose Event handler this pointer is used to set this global variable to False.
I didn't expect to win a price for maximum elegance, but this works now for many years.

Quote
Therefore, I would not implement the OnClose handler of TSettingsForm as a method of TSettingsForm itself, but as a method of TMainForm which knows about the name of the instance:
This is an interesting approach. However I use a couple of non modal Forms in and out of common units (which can be used by many programs), which have no access to a MainForm. For them I will stay with my 'old' solution.

Quote
I already had an event handler TForm1.FormClose(). Inside I did never change var 'CloseAction'. I printed it and it contains 'caFree'.
This is strange. Look at the sources of TCustomForm.Close: CloseAction is caFree only for the MainForm.
Form1 is here the 'MainForm', as far as I understand, because it is created in the main source file via 'Application.CreateForm(TForm1, Form1)'.



Hello alpine and 440bx,
because I know, how much I don't know, especially about free'ing classes or Forms, I want to dig some more time...

But of course it can't be bad to do a memory test as you described: I will create a large zip or 7zip archive with >= 600 MB and then verify this archive by the zip or 7zip program >= 6 times.

440bx

  • Hero Member
  • *****
  • Posts: 5805
But of course it can't be bad to do a memory test as you described: I will create a large zip or 7zip archive with >= 600 MB and then verify this archive by the zip or 7zip program >= 6 times.
I think it's a good prophylactic move.  It removes that possibility out of the equation.  never know.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
I found some suspicious source code used for a potentially non modal Form:
 - the Form is created via FormPV:=TFormPV.CreateNew(Application), because in the future it shall be possible to use this Form in non modal mode (and if the program terminates and a non modal form is still open, this Form is closed automatically).
 - but the Form variable is a local variable (because non modal mode is not yet implemented)
 - the Form is always displayed via self.ShowModal (because non modal mode is not yet implemented)
 - after this self.Free() is called.

I called my procedure show_Application_Components() - it displays the names of all components in Application.Components[] and is shown in reply #5 - directly before and after above self.Free(). 'FormPV' is displayed before, but not after, which confirms, that self.Free() deletes 'FormPV' from Application.Components[].

My question is not, whether this is good programming style. I think, it's not and I will change it.
My question is: could this local Form variable be the root cause for the AV, which I got lately during program termination?
To remember: the AV occured, while Application.DoBeforeFinalization() - also shown in reply #5 - was executed, which calls Free() for all components in Application.Components[].

From my understanding this local Form variable is although no good style, but definitely save and can not be the root cause for the AV. Do you agree?

wp

  • Hero Member
  • *****
  • Posts: 13200
Do you want to say that FormPV is created in a local procedure, shown modally and destroyed there? Like this:
Code: Pascal  [Select][+][-]
  1. procedure Something;
  2. var
  3.   FormPV: TFormPV;
  4. begin
  5.   FormPV := TFormPV.CreateNew(Application);
  6.   try
  7.     FormPV.ShowModal;
  8.   finally
  9.     FormPV.Free;
  10.   end;
  11. end;
I cannot imagine that this could pose any problem, it is standard in many of my applications.

 

TinyPortal © 2005-2018