Lazarus

Programming => LCL => Topic started by: Hartmut on June 11, 2020, 11:19:31 am

Title: [SOLVED] Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 11:19:31 am
Info: I wrote a short summary about the root cause of the problem and the solution in reply #34

Short overview:
I have created a common class to display infos in a TMemo on a dynamically created Form and use this class in various programs. The dynamic Form has a Button too. In former programs I used this Button only to close the Form as usual. Now I want to use this Button to allow to abort a running task.

The problem:
If this ABORT-Button is not pressed (the task runs until its normal end), I can close the dynamic Form with FreeAndNil() and everything is fine. But when the ABORT-Button was pressed, the same FreeAndNil() creates an Access violation.

And now the details:
Here follows the common class with some explanations:
Code: Pascal  [Select][+][-]
  1. unit LCL_lib; {extract of a common library for LCL functions}
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses Classes, Forms, StdCtrls, Controls, SysUtils;
  8.  
  9. type  {shows a Memo on a dynamically created Form. Has in the real world much
  10.        more features, but was here reduced to a minimum only for this problem}
  11.    TFormMM = class(TForm)
  12.       procedure show_Memo(x0,y0, xb,yh: integer; modal: boolean;
  13.                           var activ: boolean);
  14.       procedure Memo_Button_Click(Sender: TObject);
  15.       procedure CloseMM(Sender: TObject; var CloseAction: TCloseAction);
  16.          public
  17.       BTN: TButton;
  18.       MM: TMemo;
  19.          private
  20.       pActiv: ^boolean; {is reset to 'False' in "OnClose" Event = CloseMM()}
  21.    end;
  22.  
  23. procedure init_Memo(var FormMM: TFormMM);
  24. procedure free_Memo(var FormMM: TFormMM);
  25.  
  26. implementation
  27.  
  28. procedure TFormMM.show_Memo(x0,y0, xb,yh: integer; modal: boolean;
  29.                             var activ: boolean);
  30.    {creates a Memo and a Button on a dynamic Form}
  31.    begin
  32.    pActiv:=@activ;            {store Flag to control when Form has been closed}
  33.    OnClose:=@CloseMM;         {Event for closing the Form}
  34.  
  35.    SetBounds(x0,y0, xb,yh);   {set position and size of the Form}
  36.  
  37.    BTN:=TButton.Create(self); {create ABORT-Button: }
  38.    BTN.Caption:='&ABORT';
  39.    BTN.Anchors:=[akTop,akRight];
  40.    BTN.Top:=20;
  41.    BTN.Left:=self.Width-85;
  42.    BTN.OnClick:=@Memo_Button_Click;
  43.    BTN.Parent:=self;
  44.  
  45.    MM:=TMemo.Create(self);    {create Memo: }
  46.    MM.Parent:=self;
  47.    MM.Align:=alClient;
  48.    MM.BorderSpacing.Right:=95;
  49.  
  50.    if modal then ShowModal    {waits until the Form has been closed}
  51.             else Show;        {displays Form and returns immediately}
  52.    end;
  53.  
  54. procedure TFormMM.Memo_Button_Click(Sender: TObject);
  55.    begin
  56.    writeln('Memo_Button_Click() called');
  57.    Close;  {Button ABORT closes the Form via "OnClose" Event = CloseMM()}
  58.    end;
  59.  
  60. procedure TFormMM.CloseMM(Sender: TObject; var CloseAction: TCloseAction);
  61.    {called by Event "OnClose", if ABORT-Button is pressed or ALT-F4 or "x" is
  62.     clicked in the upper right corner}
  63.    begin
  64.    writeln('CloseMM(): ComponentCount=', Application.ComponentCount);
  65.    if pActiv <> nil then pActiv^:=false; {reset a flag that the Form is closed}
  66.  
  67.    CloseAction:=caFree; {informs the framework that you want the Form to be
  68.       destroyed; The framework defaults to CloseAction=caHide for all Forms
  69.       unless the Form is the applications main Form in which case it defaults
  70.       to caFree}
  71.    end;
  72.  
  73. procedure init_Memo(var FormMM: TFormMM);  {common proc to create the Form}
  74.    begin
  75.    FormMM:=TFormMM.CreateNew(Application); {see explanation in my post}
  76.    end;
  77.  
  78. procedure free_Memo(var FormMM: TFormMM);  {common proc to free the Form}
  79.    begin
  80.    FreeAndNil(FormMM);
  81.    end;
  82.  
  83. end.

And here a short demo to demonstrate the problem:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses Classes, SysUtils, Forms, StdCtrls, LCL_lib;
  8.  
  9. type
  10.  TForm1 = class(TForm)
  11.   Button1: TButton;
  12.   Button2: TButton;
  13.   procedure Button1Click(Sender: TObject);
  14.   procedure Button2Click(Sender: TObject);
  15.  private
  16.  public
  17.  end;
  18.  
  19. var Form1: TForm1;
  20.  
  21. implementation
  22.  
  23. {$R *.lfm}
  24.  
  25. var memoopen: boolean; {must be a global variable. Controls if the Form has been closed}
  26.  
  27. procedure TForm1.Button1Click(Sender: TObject);  {Button "Start": }
  28.    var FormMM: TFormMM;
  29.        i: integer;
  30.    begin
  31.    init_Memo(FormMM); {opens a dynamic Form with a Memo and a Button}
  32.    FormMM.show_Memo(100,130,500,200, false, memoopen); {modal=false}
  33.    memoopen:=true; {Flag, that ABORT-Button has not yet pressed}
  34.  
  35.    for i:=40 downto 1 do   {simulate the "real world": some work is done}
  36.       begin                {and some infos are sent to the Memo: }
  37.       if i mod 10 = 0 then
  38.          FormMM.MM.Lines.Add('you have ' + IntToStr(i div 10) +
  39.                              ' seconds to press "ABORT" button...');
  40.       Application.ProcessMessages;
  41.       sleep(100);
  42.       if not memoopen then BREAK; {if ABORT-Button was pressed}
  43.       end;
  44.  
  45.    writeln('1) memoopen=', memoopen, ' ComponentCount=', Application.ComponentCount);
  46.    if memoopen then FormMM.Close; {only if not already closed by ABORT-Button}
  47.    writeln('2) memoopen=', memoopen, ' ComponentCount=', Application.ComponentCount);
  48.    free_Memo(FormMM); {here the Access violation occurs}
  49.    writeln('------------');
  50.    end;
  51.  
  52. procedure TForm1.Button2Click(Sender: TObject);  {Button "Close": }
  53.    begin
  54.    Close;
  55.    end;
  56.  
  57. end.
   
Results of the demo:
Run the program and press Start-Button. Do not press ABORT-Button. You will see after 4 seconds in the Console:
1) memoopen=TRUE ComponentCount=2
CloseMM(): ComponentCount=2
2) memoopen=FALSE ComponentCount=2


Run the program again and press Start-Button. Press the ABORT-Button in the first 4 seconds. You will see in the Console:
Memo_Button_Click() called
CloseMM(): ComponentCount=2
1) memoopen=FALSE ComponentCount=1
2) memoopen=FALSE ComponentCount=1
TApplication.HandleException Access violation


My question:
Obviously, if the ABORT-Button is not pressed, Application.ComponentCount=2 and because of memoopen is True, my program calls FormMM.Close() which calls CloseMM(), which here does not change Application.ComponentCount, which I guess is the precondition that free_Memo() has no problems.
But if the ABORT-Button is pressed, CloseMM() now reduces Application.ComponentCount to 1, which I guess is the reason for the Access violation.
So please, why does CloseMM() behave in this different way? And how can I solve this problem?

In this demo, as a workaround I could call free_Memo() only if Application.ComponentCount=2, but in the real world I have more than 2 situations and I want to understand the problem...

Versions:
I have the same results with several Lazarus versions on different OS:
 - Windows 7 (32-bit):
   Laz 1.8.4/FPC 3.0.4 + Laz 2.0.6/FPC 3.0.4 + Laz 2.1.0 rev=62449 / FPC 3.3.1 rev=43796
 - Linux Ubuntu 18.04 (64-bit):
   Laz 1.8.4/FPC 3.0.4 + Laz 2.0.6/FPC 3.0.4

Thanks for your help in advance. I attached my demo.
Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 11, 2020, 12:03:21 pm
I didn't take a look at the src, but if you use 1. WND.RELEASE and 2. WND:= NIL does it change anything ???
Title: Re: Access violation when freeing a dynamically created Form
Post by: Bart on June 11, 2020, 12:57:35 pm
In the close action you free the form, then you free it again in another place.

Bart
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 01:08:11 pm
Thank you very much RAW for that suggestion.

I was not sure what you meant with WND.RELEASE and WND:=NIL. First I thought 'WND' should be a part of 'FormMM', but this did not compile (I'm not so familiar with the LCL).

Then I replaced in free_Memo() the FreeAndNil(FormMM) with FormMM.Release(). In my little demo this solved the problem! But in my real world program, where I have 3 situations, 1 of 2 problems was solved now, but the 2nd not. The 3 situations are:

1) the real world program has in the Memo-Form an additional CheckBox "keep me open". When checked, the Memo-Form is not closed automatically at the end of the working task (so you have time to inspect the results, until you press the ABORT-Button).
With FreeAndNil(FormMM) I got an Access violation, with FormMM.Release() this problem was solved.

2) the program runs to its normal end, because ABORT is not pressed. In this situation I never had problems.

3) while the working task is executed, ABORT is pressed, which caused an Access violation. In this situation FormMM.Release() did not prevent the Access violation.

Then I replaced in free_Memo() the FormMM.Release() with FormMM:=nil and now all 3 situations work fine without any Access violation!

So I have those questions left:
 - is 'FormMM:=nil' a save way to 'free' a Form and all of its memory? In my real program the dynamic Memo-Form is opened and closed many times and I want to avoid memory leaks.
 - why does 'FormMM:=nil' work and FreeAndNil() cause an Access violation?
 - in which other cases should I use FreeAndNil() and in which cases 'FormMM:=nil'? Is one of them generally good and the other not?
 
Thanks for any explainations.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 01:20:41 pm
In the close action you free the form, then you free it again in another place.

Hmmm, I can't find the source lines which you mean. And from the console output (see above) for me it seems to be sure, that CloseMM(), the OnClose-Event, is always only called once, not twice.
Can you please be more detailed? Thanks.
Title: Re: Access violation when freeing a dynamically created Form
Post by: rvk on June 11, 2020, 01:25:55 pm
So I have those questions left:
 - is 'FormMM:=nil' a save way to 'free' a Form and all of its memory? In my real program the dynamic Memo-Form is opened and closed many times and I want to avoid memory leaks.
 - why does 'FormMM:=nil' work and FreeAndNil() cause an Access violation?
 - in which other cases should I use FreeAndNil() and in which cases 'FormMM:=nil'? Is one of them generally good and the other not?
Just use FormMM.Free;

You could do FormMM := nil after the .Free but it shouldn't be necessary (if you don't check for unassigned).

Don't use only FormMM := nil because that will lead to massive memory leaks.

In Lazarus set Project > Project Options > Debugging and check the box for "Use heaptrc unit" to make sure you don't have leaks.
If you do, you get a dialog at the end of your program of which components are not freed.
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 11, 2020, 01:59:30 pm
The problem is the Action := caFree in the OnClose handler of TFormMM. It destroys the form but does not set the formvariable to nil. You close the form in the OnClick handler ABORT button, and, because of caFree, this destroys the form but leaves FormMM non-nil which allows your free_memo to try to destry it again.

Since everything happens in a single routine, I remove the caFree and rely on the Free_memo alone.

Alternatively the OnClose handler of FormMM must be provided by the calling program which can simultaneously set FormMM to nil.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 02:11:05 pm
Just use FormMM.Free;
I tried FormMM.Free() before I wrote this Topic, it makes no difference to FreeAndNil(FormMM), in both my small demo and my real world program, so unfortunately it does not help.

Quote
You could do FormMM := nil after the .Free but it shouldn't be necessary (if you don't check for unassigned).
Because FormMM.Free can cause the Access violation, I must avoid it and having a FormMM:=nil after it I can see no improvement.

Quote
Don't use only FormMM := nil because that will lead to massive memory leaks.
That's exactly what I was afraid of...

Quote
In Lazarus set Project > Project Options > Debugging and check the box for "Use heaptrc unit" to make sure you don't have leaks.
If you do, you get a dialog at the end of your program of which components are not freed.
Here comes the result (with the original FreeAndNil(FormMM) in free_Memo()):

Code: [Select]
Memo_Button_Click() called
CloseMM(): ComponentCount=2
1) memoopen=FALSE ComponentCount=1
2) memoopen=FALSE ComponentCount=1
TApplication.HandleException Access violation
  Stack trace:
  $000000000043078D
  $00000000004627F6
  $0000000000542CC2
  $00000000005A5CEA
  $00000000005A6533
  $00000000005A5BD6
  $0000000000430CBF
  $000000000053442D
  $0000000000709722
  $000000000071B781
  $00007F5C36CA010D
WARNING: TButton.Destroy with LCLRefCount>0. Hint: Maybe the component is processing an event?
Heap dump by heaptrc unit
1174 memory blocks allocated : 123681/125696
1168 memory blocks freed     : 123293/125288
6 unfreed memory blocks : 388
True heap size : 196608
True free heap : 195264
Should be : 195432
Call trace for block $00007F5C3827C540 size 33
  $000000000045B1BD
  $000000000045C6B8
  $000000000045B6AD
  $000000000070979C
  $000000000071B781
  $000000000071B781
  $000000000071B781
  $00000000005A5CEA
Call trace for block $00007F5C3835B2C0 size 121
  $00000000004C7A56
  $000000000045C67F
  $000000000045B6AD
  $000000000070979C                                                                       
  $000000000071B781                                                                       
  $000000000071B781                                                                       
  $00000000005387A0                                                                       
  $0000000000539F78                                                                       
Call trace for block $00007F5C3827BE80 size 42                                           
  $000000000045B6AD
  $000000000070979C
  $000000000071B781
  $000000000070979C
  $000000000071B781
  $000000000070979C
  $000000000071B781
  $000000000071B781
Call trace for block $00007F5C3835B3E0 size 128
  $0000000000431B81
  $00000000004627F6
  $0000000000542CC2
  $00000000005A5CEA
  $00000000005A6533
  $00000000005A5BD6
  $0000000000430CBF
  $000000000053442D
Call trace for block $00007F5C3827DD40 size 40
  $0000000000431B81
  $00000000004627F6
  $0000000000542CC2
  $00000000005A5CEA
  $00000000005A6533
  $00000000005A5BD6
  $0000000000430CBF
  $000000000053442D
Call trace for block $00007F5C3827DE00 size 24
  $00000000004627F6

This is the 1st time I used unit Heaptrc. Does this give you more informations?
Title: Re: Access violation when freeing a dynamically created Form
Post by: rvk on June 11, 2020, 02:17:29 pm
This is the 1st time I used unit Heaptrc. Does this give you more informations?
Yes, it tells you there is a leak.

But I didn't notice the CloseAction := caFree.
So follow the instructions of wp.

When using CloseAction := caFree you shouldn't free anything yourself.
So either completely remove the freeandnil or remove the caFree.

There shouldn't be any need for setting FormMM to nil.
You'r not using it anymore after doing that so it has no use.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 04:32:35 pm
Hello wp again, "old friend". Thank you very much for your reply.

The problem is the Action := caFree in the OnClose handler of TFormMM. It destroys the form but does not set the formvariable to nil. You close the form in the OnClick handler ABORT button, and, because of caFree, this destroys the form but leaves FormMM non-nil which allows your free_memo to try to destry it again.
I think now I understand the problem. As often I'm impressed how much details you know.

Quote
Since everything happens in a single routine, I remove the caFree and rely on the Free_memo alone.
If I understand you correctly, you suggest to remove the 'CloseAction:=caFree' completely. But if I remember correctly, this command was essential neccessary to implement the feature, that if the main program terminates and the dynamic Memo-Form is still open, that this Memo-Form is automatically closed. I use this feature in a couple of programs and don't want to loose it, if possible.

Quote
Alternatively the OnClose handler of FormMM must be provided by the calling program which can simultaneously set FormMM to nil.
As rvk wrotes (see below), FormMM:=nil seems not to be neccessary?

...
When using CloseAction := caFree you shouldn't free anything yourself.
So either completely remove the freeandnil or remove the caFree.

There shouldn't be any need for setting FormMM to nil.
You'r not using it anymore after doing that so it has no use.
Thanks a lot again for that infos rvk.
Because I think I need the 'CloseAction:=caFree' (see above), I removed FreeAndNil() and free_Memo() completely and made a test with Heaptrc:

In my little demo I pressed the Start-Button several times and sometimes I pressed ABORT and sometimes not. After the Close-Button Heaptrc showed everything is OK!

But in my real world program I had heavy fights with Heaptrc:
 - after situation 1 (Memo keeps open because of CheckBox, see above) it was not possible to stop the program with it's Close-Button. From writeln() commands I could see, that the Close-Button-Event and the OnClose-Event of Form1 both were called - but the program did not terminate. So it was not possible to see the Heaptrc result :-(
 - after situations 2+3 (see above) Heaptrc showed "thousands" of lines (more than the console buffer can hold) like:
Code: [Select]
...
An unhandled exception occurred at $0000000000437117:
EAccessViolation:
  $0000000000437117

An unhandled exception occurred at $0000000000437117:
EAccessViolation:
  $0000000000437117

An unhandled exception occurred at $0000000000437117:
EAccessViolation:
Speicherzugriffsfehler (Speicherabzug geschrieben)
I tried different Lazarus versions, but no difference. This was on Linux (Ubuntu 18.04).

Then I tried the same Lazarus versions (Laz 1.8.4/FPC 3.0.4 + Laz 2.0.6/FPC 3.0.4) on Windows 7 and then:
 - the problem after situation 1 was the same but
 - the problem after situations 2+3 did not occur and Heaptrc showed everything is OK.

Obviously Heaptrc has a problem on Linux, which not exists on Windows... Because this was the very 1st time I used Heaptrc, I searched for documentation, to see if I made something wrong, but in https://wiki.lazarus.freepascal.org/heaptrc I saw nothing about that.

Question:
Has anybody an idea, why Heaptrc fails after situations 2+3 (on Linux only) and after situation 1 (on Linux and Windows)?
I disabled in Project Options / Debugging all 6 checks: -Ci -Cr -Co -Ct -CR -Sa, but this made no difference.
Some of the units I use in my real world program are in {$mode TP} (because they were started decades ago). Could this mix be a reason?

If I could get Heaptrc to work also after situation 1 and see, that everything is OK, then simply removing FreeAndNil() and free_Memo() completely would be a very fine solution :-)

Solution for above Heaptrc-problems (added June 18th 2020):
Meanwhile I could solve the 2 problems with Heaptrc: After Heaptrc now works perfectly, I made a couple of tests with my solution to remove FreeAndNil() in free_Memo() and the results were always "everything ok".
Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 11, 2020, 05:34:00 pm
Hi,
what I meant in the first post was "use both together".

The best way to free a form is this (in my eyes):
Code: Pascal  [Select][+][-]
  1. FORM.Release;
  2. FORM:= Nil;

Maybe you don't need FORM:= Nil right now, but maybe in the future and FORM:= Nil isn't that much additional typing, is it?  :)
I would always use RELEASE if you don't need the form anymore...
Title: Re: Access violation when freeing a dynamically created Form
Post by: Bart on June 11, 2020, 05:49:23 pm
In the close action you free the form, then you free it again in another place.

Hmmm, I can't find the source lines which you mean. And from the console output (see above) for me it seems to be sure, that CloseMM(), the OnClose-Event, is always only called once, not twice.
Can you please be more detailed? Thanks.

In Close_MM you do
Code: Pascal  [Select][+][-]
  1.   CloseAction:=caFree;
(Line 67)

Bart
Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 11, 2020, 06:06:42 pm
by the way:
Code: Pascal  [Select][+][-]
  1. FormMM:=TFormMM.CreateNew(Application);
If you setup an owner for the form (SELF, APPLICATION) then let the owner free it...  :)
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 11, 2020, 06:11:33 pm
I played with the program again and found two solutions.

Solution #1
This is very simple: Do not call Free_Memo at all. Since the CloseAction of FormMM is caFree the form will be destroyed automatically whenever the user closes the form or when FormMM.Close is called. FormMM is a local variable within the procedure where FormMM is created, therefore you cannot access it anywhere.

Solution #2
I think this is the real issue behind your question. It is more general than Solution #1 because you can access FormMM also from outside the procedure where it is created; and this is probably what you want because the form is opened with Show(), not with ShowModal().

At first, in order to access FormMM from everywhere, the variable must not be local but global. And it must be initialized with nil as an indicator that the form exists or not:

Code: Pascal  [Select][+][-]
  1. var
  2.   FormMM: TFormMM = nil;

Closing a non-modal form is difficult if there exists a variable somewhere pointing to it, because when there is caFree in the OnClose handler that variable is not reset to nil to indicate that it should not be accessed any more.

I think a rather general way to handle this situation is to use an OnClose handler that is owned by the class which uses the variable. In other words, do not use the OnClose which you can create by the form designer when the non-modal form (FormMM) is active but write the Onclose handler in the form which uses FormMM, TForm1:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.CloseMM(Sender: TObject; var CloseAction: TCloseAction);
  2. begin
  3.   if Sender = FormMM then
  4.   begin
  5.     CloseAction:=caFree;
  6.     FormMM := nil;
  7.   end;
  8. end;

Assign this "foreign" handler to FormMM manually when it is created:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);  {Button "Start": }
  2. var
  3.   i: integer;
  4. begin
  5.   if FormMM = nil then          // wp: Make sure that FormMM exists only once.
  6.     init_Memo(FormMM);
  7.   FormMM.OnClose := @CloseMM;  // wp: Assign the OnClose handler defined within the context of TForm1 to FormMM
  8.   ...

As you can see I create the FormMM only when it does not yet exist to avoid creating it several times which will kill the logics behind this mechanism.

Your original OnClose handler was:

Code: Pascal  [Select][+][-]
  1. procedure TFormMM.CloseMM(Sender: TObject; var CloseAction: TCloseAction);
  2.    {called by Event "OnClose", if ABORT-Button is pressed or ALT-F4 or "x" is
  3.     clicked in the upper right corner}
  4.    begin
  5.    writeln('CloseMM(): ComponentCount=', Application.ComponentCount);
  6.    if pActiv <> nil then pActiv^:=false; {reset a flag that the Form is closed}
  7.  
  8.    CloseAction:=caFree; {informs the framework that you want the Form to be
  9.       destroyed; The framework defaults to CloseAction=caHide for all Forms
  10.       unless the Form is the applications main Form in which case it defaults
  11.       to caFree}
  12.    end;

The new handler so far has only covered the CloseAction instruction in there, but there are other commands which must be dealt with, too. When you look at the source code of the Close method of TForm you see a variety of instructions, one of them is DoCloseAction which fires the OnClose event. This method is virtual, which means that you can replace it by a new method in TFormMM and put the missing commands in there:

Code: Pascal  [Select][+][-]
  1. procedure TFormMM.DoClose(var CloseAction: TCloseAction);
  2. begin
  3.   writeln('CloseMM(): ComponentCount=', Application.ComponentCount);
  4.   if pActiv <> nil then pActiv^:=false;
  5.   inherited;  // wp: this fires the OnClose event and therefore will run TForm1.CloseMM().
  6. end;

With these changes your program does no longer crash for me. I must say, however, that I do not fully understand what you want to achieve. Therefore, I think there may be simpler solutions to this problem.


Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 08:07:24 pm
Thanks a lot to RAW, Bart and wp for your new replies. I will answer them one by one to avoid posts that get too long.

Hi, what I meant in the first post was "use both together".
The best way to free a form is this (in my eyes):
Code: Pascal  [Select][+][-]
  1. FORM.Release;
  2. FORM:= Nil;
Maybe you don't need FORM:= Nil right now, but maybe in the future and FORM:= Nil isn't that much additional typing, is it?  :)
I would always use RELEASE if you don't need the form anymore...

I had tried 'FORM.Release' in my real world program in reply #3 (see above) and in situation 3) I still got an Access violation. Now I tried it again with 'FORM:=Nil' after 'FORM.Release' but of course the same Access violation occured. So 'FORM.Release' doesn't help in this case.

When I started with this common class, I used FormMM.Free(), because I use Free() normally for other classes too. Sometimes later I had to change it in FreeAndNil(), but I can't remember why. Now I learn that sometimes FormMM.Release is better (but not in my current case). And that an additional FormMM:=nil sometimes might be neccessary. While FormMM:=nil alone worked on the 1st view, it can / will lead to massive memory leaks. And I saw that Destroy() and DestroyWnd() exist also, which sound like acting related.

When I look in the documentation https://lazarus-ccr.sourceforge.io/docs/lcl/forms/tform.html I don't find Release() or Free() or FreeAndNil() there. I must try the chain of ancestors and in https://lazarus-ccr.sourceforge.io/docs/lcl/forms/tcustomform.html I find e.g.:
   procedure Release; Marks the form for destruction.
   destructor Destroy; override; -nothing more-
   procedure DestroyWnd; override; Destroys the interface object (widget).
With those, much to small "documentation" I'll never see the chance to understand the important differences for so many alternatives to free a Form. This is very, very frustrating for me :-(


But of course that's not your fault, I'm happy for your help.

by the way:
Code: Pascal  [Select][+][-]
  1. FormMM:=TFormMM.CreateNew(Application);
If you setup an owner for the form (SELF, APPLICATION) then let the owner free it...  :)

Yes, but at which time is that 'free' done, when I use 'Application' like here? My guess is, that 'Application' does this only once at the very end of my program (correct or wrong?). But the dynamic Memo-Form I want to open and close often, before the program terminates. Does this work correctly, if this dynamic Form is only free'd once at the end?
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 08:23:55 pm
In the close action you free the form, then you free it again in another place.
Hmmm, I can't find the source lines which you mean. And from the console output (see above) for me it seems to be sure, that CloseMM(), the OnClose-Event, is always only called once, not twice. Can you please be more detailed? Thanks.

In Close_MM you do
Code: Pascal  [Select][+][-]
  1. CloseAction:=caFree;
(Line 67)

Yes, from my understanding this Line 67 is the only one, where the dynamic Memo-Form is closed and from the showed Console output (see original post) this line seems to be called only once. But I understood your "In the close action you free the form, then you free it again in another place" that you mean, that there are either 2 different source lines or 1 sorce line called twice? Did I misunderstand you?
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 11, 2020, 10:01:52 pm
I played with the program again and found two solutions.
Thanks a lot for this very detailed answer. It was hard stuff for me trying to understand all. But I will start to answer beginning with your end:

Quote
I must say, however, that I do not fully understand what you want to achieve. Therefore, I think there may be simpler solutions to this problem.
This common class has grown historically over the years (in reality it can do much more, I minimized it for this demo). I use this class in various programs (one of them uses 2 different Instances, which can be open at the same time). So every change, which is not only inside this class, could cause very much work to adapt (and test) it in all programs, which use this class.
In my real world program 'FormMM' is a global variable, because many procs need it. But there is a "main procedure" which controls everything and I can assure, that init_Memo() always is called first and free_Memo() always is called last. But it is important, that this "main procedure" can be called multiple times, before the program terminates.

I want badly to use your Solution #1, because it is much easier (and would not affect other existing programs using this common class, because I can keep free_Memo() and make it only empty). My experiments with Heaptrc (see reply #9) were not successful on Linux, but on Windows I got "everything is OK" for 2 of 3 situations. This looks optimistic...

So my question to you is: if I stay with using 'CreateNew(Application)' in init_Memo() and stay with 'CloseAction:=caFree' in CloseMM() and only make free_Memo() empty: would this be a correct way, if I want to call above "main procedure" multiple times, before the program terminates?

Thanks a lot for your valuable help (and good night for today).
Title: Re: Access violation when freeing a dynamically created Form
Post by: Bart on June 11, 2020, 11:06:55 pm
Yes, from my understanding this Line 67 is the only one, where the dynamic Memo-Form is closed and from the showed Console output (see original post) this line seems to be called only once. But I understood your "In the close action you free the form, then you free it again in another place" that you mean, that there are either 2 different source lines or 1 sorce line called twice? Did I misunderstand you?

Your Abort Button (name: BTN I think) closes the form.
At that point the form gets freed (because you set ClosAction to caFree), but not nilled.
You then break the loop in the main form and call free_Memo(FormMM);
This will call Free on an already destroyed form, hence the AV.

This is as far as I cab understand the code flow, without an actual compilable example.

Add a debug statement in the destructor of this form, so you can see when it is called, my guess is before you call free_memo(FormMM).

Bart
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 11, 2020, 11:57:52 pm
So my question to you is: if I stay with using 'CreateNew(Application)' in init_Memo() and stay with 'CloseAction:=caFree' in CloseMM() and only make free_Memo() empty: would this be a correct way, if I want to call above "main procedure" multiple times, before the program terminates?
Basically ok, but not very nice. Because having an empty procedure will confuse you next year when you have forgotten why you did this. What is the problem to delete it? The compiler will tell you where it is called.

When you need the form again and again there is a third option: Do not destroy it at all. Remove CloseAction := caFree and you will only hide the form, the form variable will always be valid - perfect. In this case Init_Memo must check whether the form is nil to create it or whether it is not nil to show it:

Code: Pascal  [Select][+][-]
  1. procedure Init_Memo(var FormMM: TFormMM);
  2. begin
  3.   if FormMM = nil then
  4.     FormMM := TFormMM.CreateNew(Application)
  5.   else
  6.     FormMM.Show;

But note that in this case your Show_Memo will be a problem because it will create the button and the memo again and again. Why don't you do this in the Init_Memo?

Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 12, 2020, 04:36:02 am
Why not reconsider the whole structure ???  :)

Main Form
Code: Pascal  [Select][+][-]
  1. UNIT Unit1;  // main application
  2. {$MODE OBJFPC}{$H+}
  3. INTERFACE
  4. USES
  5.  Classes,SysUtils,Forms,Controls,Graphics,StdCtrls,uMemoWnd;
  6. TYPE
  7.  TWndMain = Class(TForm)
  8.   Button1:TButton;
  9.   Button2:TButton;
  10.   Procedure Button1Click   (Sender:TObject);
  11.   Procedure Button2Click   (Sender:TObject);
  12.   Procedure WndMemoOnClose (Sender:TObject;Var Quit:TCloseAction);
  13.  PRIVATE
  14.   WndMemo:TWndMemo;
  15.  End;
  16. VAR
  17.  WndMain:TWndMain;
  18. IMPLEMENTATION
  19. {$R *.LFM}
  20.  
  21. Procedure TWndMain.Button1Click(Sender:TObject);
  22. Begin
  23.   If Not Assigned(WndMemo)
  24.   Then
  25.    Begin
  26.     WndMemo:= TWndMemo.CreateNew(Self,0,0,300,200);
  27.     WndMemo.OnClose:= @WndMemoOnClose;
  28.     WndMemo.Show;
  29.    End;
  30. End;
  31.  
  32. Procedure TWndMain.Button2Click(Sender:TObject);
  33. Begin
  34.   If Not Assigned(WndMemo)
  35.   Then
  36.    Begin
  37.     WndMemo:= TWndMemo.CreateNew(Nil,0,0,300,200);
  38.     Try
  39.      WndMemo.ShowModal;
  40.     Finally
  41.      WndMemo.Release;
  42.      WndMemo:= Nil;
  43.     End;
  44.    End;
  45. End;
  46.  
  47. // only necessary with show
  48. Procedure TWndMain.WndMemoOnClose(Sender:TObject;Var Quit:TCloseAction);
  49. Begin
  50.   WndMemo.Release;
  51.   WndMemo:= Nil;
  52. End;
  53.  
  54. END.

Memo Window
Code: Pascal  [Select][+][-]
  1. UNIT uMemoWnd;  // create a form with a memo and a button without res file
  2. {$MODE OBJFPC}{$H+}
  3. INTERFACE
  4. USES
  5.  Classes,SysUtils,Forms,StdCtrls;
  6. TYPE
  7.  TWndMemo = Class(TForm)
  8.  PRIVATE
  9.   Memo:TMemo;
  10.   Btn:TButton;
  11.   Procedure BtnClick (Sender:TObject);
  12.  PUBLIC
  13.   Constructor CreateNew(AOwner:TComponent;iX,iY,iW,iH:Integer);Overload;
  14.   Destructor  Destroy;Override;
  15.  End;
  16. IMPLEMENTATION
  17.  
  18. Constructor TWndMemo.CreateNew(AOwner:TComponent;iX,iY,iW,iH:Integer);
  19. Begin
  20.   Inherited CreateNew(AOwner);
  21.   // wnd
  22.   Caption:= 'Memo Window';
  23.   SetBounds(iX,iY,iW,iH);
  24.   // memo
  25.   Memo:= TMemo.Create(Self);
  26.   Memo.SetBounds(0,0,ClientWidth,ClientWidth Div 2);
  27.   Memo.Parent:= Self;
  28.   // button
  29.   Btn:= TButton.Create(Self);
  30.   Btn.Caption:= 'Close MemoWindow';
  31.   Btn.SetBounds(0,Memo.Height,ClientWidth,ClientHeight-Memo.Height);
  32.   Btn.OnClick:= @BtnClick;
  33.   Btn.Parent:= Self;
  34. End;
  35.  
  36. Destructor TWndMemo.Destroy;
  37. Begin
  38.   // free something if necessary ...
  39.   Inherited Destroy;
  40. End;
  41.  
  42. Procedure TWndMemo.BtnClick(Sender:TObject);
  43. Begin
  44.   Close;
  45. End;
  46.  
  47. END.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 12, 2020, 04:01:59 pm
Thanks a lot to Bart, wp and RAW for your new replies and your help. I will answer them again one by one to avoid posts that get too long.

Your Abort Button (name: BTN I think) closes the form.
At that point the form gets freed (because you set ClosAction to caFree), but not nilled.
You then break the loop in the main form and call free_Memo(FormMM);
This will call Free on an already destroyed form, hence the AV.
OK, now I understand what you mean. Thanks for clarification.
This explains, why the AV occurs, if the Abort-Button had been pressed.
But if the Abort-Button was not pressed, I call FormMM.Close() instead in line 46 =
   if memoopen then FormMM.Close; {only if not already closed by ABORT-Button}
and in this case the AV does not occur later in free_Memo() in line 48...

I don't understand the difference. In both cases FormMM.Close(), which calls CloseMM(), is exactly called once (as we see in the Console output). Why does the variant without Abort-Button work (where FormMM.Close() is called once in line 46 of Unit1) and the variant with Abort-Button does not (where FormMM.Close() is called once in line 57 of unit LCL_lib)?

And pleasae notice, that if the Abort-Button is pressed, that in this case Application.ComponentCount is reduced to 1, while if the Abort-Button is not pressed, Application.ComponentCount stays at 2. Who (and why) reduces Application.ComponentCount in just this case, where later the AV occurs?

Quote
This is as far as I cab understand the code flow, without an actual compilable example.
I attached a compilable demo at my 1st post, did you see it?

Quote
Add a debug statement in the destructor of this form, so you can see when it is called, my guess is before you call free_memo(FormMM).
That sounds interesting. I was not sure, what you meant exactly: add a destructor to 'TFormMM' and hope that it's called automatically or add an OnDestroy-Event? I tried the latest:

Code: Pascal  [Select][+][-]
  1. procedure TFormMM.show_Memo(...);  
  2.    begin                            
  3.    ...
  4.    OnDestroy:=@FormDestroy;
  5.    ...
  6.    end;
  7.    
  8. procedure TFormMM.FormDestroy(Sender: TObject);
  9.    begin
  10.    writeln('FormDestroy()');
  11.    end;  
       
And I added a writeln() in free_Memo() and a 3rd writeln() of memoopen and Application.ComponentCount in line 48 of Unit1, after free_Memo() was called.   

Now the Console output without Abort-Button is:
1) memoopen=TRUE ComponentCount=2
CloseMM(): ComponentCount=2
2) memoopen=FALSE ComponentCount=2
free_Memo()
FormDestroy()
3) memoopen=FALSE ComponentCount=1


and with Abort-Button is:
Memo_Button_Click() called
CloseMM(): ComponentCount=2
FormDestroy()
1) memoopen=FALSE ComponentCount=1
2) memoopen=FALSE ComponentCount=1
free_Memo()
TApplication.HandleException Access violation


From my understanding:
 - in the 2nd case, with Abort-Button, FormDestroy() seems to be called automatically by CloseMM()
 - but in the 1st case, without Abort-Button, the same CloseMM() does not call FormDestroy() automatically - it is called much later by free_Memo().
This seems to explain, why 1st case works and 2nd case not.

So my Questions to this are:
 - But why does CloseMM() react so different?
 - If we could understand this behavior, it might be very easy to add a Flag, which is set in Memo_Button_Click(), and in free_Memo() we simply skip the FreeAndNil(), if this Flag is set and everything is happy?
 - Or can we delete free_Memo() completely, because of 'CloseAction:=caFree', as rvk suggested in reply #8 and wp in reply #13? Would this be safe and clean, even if the dynamic Memo-Form is opened and closed multiple times, before the main program terminates? Do you agree to this?
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 12, 2020, 05:48:01 pm
But if the Abort-Button was not pressed, I call FormMM.Close() instead in line 46 =
   if memoopen then FormMM.Close; {only if not already closed by ABORT-Button}
and in this case the AV does not occur later in free_Memo() in line 48...

I don't understand the difference. In both cases FormMM.Close(), which calls CloseMM(), is exactly called once (as we see in the Console output). Why does the variant without Abort-Button work (where FormMM.Close() is called once in line 46 of Unit1) and the variant with Abort-Button does not (where FormMM.Close() is called once in line 57 of unit LCL_lib)?
When ABORT is not pressed the code goes into the FormMM.Close that you mention as cited. Using the debugger to trace into the LCL code I see that Form.Close calls ReleaseComponent which does not destroy the form immediately but drops the request for it in the message queue.

The same happens essentially when you call FormMM.Close in the MemoButtonClick procedure. But in the calling procedure (TForm1.ButtonClick) there is an Application.ProcessMessages which handles all commands accumulated in the message queue, among them the request to destroy FormMM.

can we delete free_Memo() completely, because of 'CloseAction:=caFree', as rvk suggested in reply #8 and wp in reply #13? Would this be safe and clean, even if the dynamic Memo-Form is opened and closed multiple times, before the main program terminates? Do you agree to this?
Again, yes. But you must be sure that the FormMM variable is set to nil afterwards. If you do not want to follow the idea with the "foreign" OnClose handler that I presented several posts above you could also replace the declaration of FormMM as a variable by a function which queries the forms of the screen and returns the (only) TFormMM instance or nil:
Code: Pascal  [Select][+][-]
  1. function FormMM: TFormMM;
  2. var
  3.   i: Integer;
  4. begin
  5.   for i := 0 to Screen.FormCount-1 do
  6.     if Screen.Forms[i] is TFormMM then
  7.     begin
  8.       Result := TFormMM(Screen.Forms[i]);
  9.       exit;
  10.     end;
  11.   Result := nil;
  12. end;
You can put this code into your LCL_lib. Using a function instead of a variable interferes with your procedures Init_Memo - use a local variable instead which will become invalid when the procedure calling Init_Memo is exited - and Free_Memo - but you should delete this procedure anyway.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 12, 2020, 05:48:31 pm
So my question to you is: if I stay with using 'CreateNew(Application)' in init_Memo() and stay with 'CloseAction:=caFree' in CloseMM() and only make free_Memo() empty: would this be a correct way, if I want to call above "main procedure" multiple times, before the program terminates?
Basically ok...
"Basically ok" sounds a bit like there are any functional restrictions... do you see any or would it be a clean and safe way? Because this solution would be much easier (see reply #16) than all other solutions, I would like very much to choose it, if it is clean and safe. Is it?

Quote
...but not very nice. Because having an empty procedure will confuse you next year when you have forgotten why you did this. What is the problem to delete it? The compiler will tell you where it is called.
Of course I would add some lines of remarks in this empty procedure, where I had so much trouble with, including the URL of this Topic. And please allow that I contradict in this single point for 2 reasons:

In my philosophy it leads to "better code", if you make use of an object or class, to assure, that in any case exactly one call to its "cleaning procedure" (e.g. its destructor or whatever exists) is made, even if this procedure (currently) might be empty.

And from my programming experience (42 years next month, with some gaps) I learned, that if you want to improve an object or class sometimes later and then the need for a "cleaning procedure" arises, that this can result in very much and very hard work, to inspect all [old] programs, which use this object / class, to find and understand all possible paths, which each program can go after initializing this object / class, to find all the correct places, where the cleaning procedure has to be inserted to assure, that it is called exactly once in every case. It can be neccessary, to set parts of a program therefor totally "to its top" and it is dangerous to add new bugs. And often this needs intensive testing (for each program).
I think it's a huge benefit, if all those programs already contain the neccessary calls to this cleaning procedure at the right places. Then you only have to recompile them and normaly need not much testing.

For my common class 'TFormMM' the cleaning procedure free_Memo() already exists and already is implemented in all referencing programs. I see nothing to win, if I delete it everywhere, only the risk of maybe having to insert them again everywhere some day.
I hope you agree?

Quote
When you need the form again and again there is a third option: Do not destroy it at all. Remove CloseAction := caFree and you will only hide the form, the form variable will always be valid - perfect.
This is an interesting approach, thanks for that idea. If I would need this dynamic Memo-Form only in my new program, I think this would be the best (simpliest) way. But because this common class is used in various programs (and can do much more than I showed in this minimalized demo) it looks much easier to only empty the free_memo(), if this would be a clean and safe way. Is it?

Quote
But note that in this case your Show_Memo will be a problem because it will create the button and the memo again and again. Why don't you do this in the Init_Memo?
I thought it would be better, to put as much code as possible in a method of a class (which could be replaced virtually, if needed) than in a global procedure. But you are right, for your third option this had to be changed.

Thanks again for your continuous help.
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 12, 2020, 05:59:43 pm
"Basically ok" sounds a bit like there are any functional restrictions... do you see any or would it be a clean and safe way? Because this solution would be much easier (see reply #16) than all other solutions, I would like very much to choose it, if it is clean and safe. Is it?
If you want an absolute statement: NO because you always can access the form variable FormMM after closing the form and you cannot even check for nil to verify that FormMM does not exist any more. But when you take measures to make sure that FormMM cannot be accessed or can be checked against NIL before access, it is safe.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 12, 2020, 09:29:55 pm
When ABORT is not pressed the code goes into the FormMM.Close that you mention as cited. Using the debugger to trace into the LCL code I see that Form.Close calls ReleaseComponent which does not destroy the form immediately but drops the request for it in the message queue.

The same happens essentially when you call FormMM.Close in the MemoButtonClick procedure. But in the calling procedure (TForm1.ButtonClick) there is an Application.ProcessMessages which handles all commands accumulated in the message queue, among them the request to destroy FormMM.
Ahhh, I think now the whole oracle is completely solved :-) Now we know, where and why the problem comes in the world and why the "same" FormMM.Close() produces those different results. Thanks a lot for finding this out. I will have to write a lot of remarks in my sources after this Topic is closed, because I learned so much and don't want to loose (forget) it.

Quote
can we delete free_Memo() completely, because of 'CloseAction:=caFree'...?
Again, yes. But you must be sure that the FormMM variable is set to nil afterwards.

If you do not want to follow the idea with the "foreign" OnClose handler that I presented several posts above you could also replace the declaration of FormMM as a variable by a function which queries the forms of the screen and returns the (only) TFormMM instance or nil:
Hmm, I have some difficulties to understand what you mean exactly.

Why is it so important to set FormMM to nil?
I'm not aware of one line of my code, where FormMM is checked for nil, and from the existing code until now I have no idea, where and why I should do this. As said, all programs have implemented a call to the "cleaning procedure" free_Memo(), when the Memo-Form is not used any longer and if I didn't implement a real bug, nobody should access FormMM after this time - only when a next init_Memo() is neccessary for the next usage of the dynamic Memo-Form.
Do you think, that if I follow this "rules", that setting FormMM:=nil nevertheless is important?
Or maybe is it important because of CreateNew(Application) in init_Memo() and / or CloseAction:=caFree in the OnClose-Event?
I just saw, what you wrote in reply #23. It makes me hope, that FormMM:=nil is not neccessary, as long as I follow those "rules". Did I understand this correctly?

Which time did you mean with "... set FormMM to nil afterwards"?
After each call of free_Memo()? Than I could place 'FormMM:=nil' very easy into free_Memo(). Shall I do that?
Or do you mean only once, when the main program terminates?

Your suggested function, which reads Screen.Forms[] looks very interesting, because it seems to be a way to access all currently "existing" Forms? Until now I only knew Application.Components[] to do something similar. I will play with Screen.Forms[] in the next days to see what it's useful for.

And your idea to replace a global class variable by a function like this is brilliant - I would never had an idea like that - and learned something more.

Long time I speculated, by which kind of magic my 'TFormMM' could be read out of Screen.Forms[] and how could it get in there. Do you mean the following:
 - init_Memo() stays as it is now
 - the caller of init_Memo() uses a local variable 'FormMM' instead of a global variable
 - thats all?
 
In this case I see 2 problems:
 - the caller of init_Memo() is the caller of all or the most methods of this class. Then there a local variable 'FormMM' exists and - with the same name - your function FormMM() in unit LCL_lib. I think, only one of them will win. Should be solvable (with additional effort).
 - as I wrote in reply #16, (currently only) one of my programs uses 2 different Instances of the Memo-Form, which can be open at the same time. I suspect that function FormMM() would not work in this case? And that the effort to solve this could be greater?

But again, I have not fully understood, if / why this additional effort with function FormMM() is actually neccessary or not. Sorry...
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 12, 2020, 09:45:37 pm
Why not reconsider the whole structure ???  :)

Main Form
...
Memo Window
...
Thank you for that suggestion. I will have a look at your code to see how it works and what I can learn from it.

But as long as I hope, that I can solve my complete problem by only deleting 1 line of code in free_Memo() - and currently it looks like that - I would prefer this solution, because it's much easier and much less work.

As I wrote, in the real world common class 'TFormMM' can do much more than I showed in this little demo, which I reduced to it's absolute minimum, only enough to demonstrate the problem. Switching class 'TFormMM' to your suggestion would cause much more work, than you maybe thought. Including to adapt all the programs, which use this class. Including a lot of testing...
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 12, 2020, 10:57:21 pm
Hmm, I have some difficulties to understand what you mean exactly.
Why is it so important to set FormMM to nil?
The key question is: Is FormMM a global variable and can it be accessed at any time? It is not in your demo program, but you said that it is in the general case. If it is not global then you may be right to control the validity of the form by status variables like "Active" or "MemoOpen" - but this must be fixed as your demo shows that this mechanism is not working correctly.

So, let's return to the global variable FormMM. At start FormMM is nil. You can create the form, and the variable FormMM points to the correct memory location. Now the user closes the form by clicking on the 'X', and after a short time the form is destroyed. But your program does not notice that the form does not exist any more, the old pointer value is still in the FormMM variable and of course, since the form is destroyed, it points to nowhere. I assume that your program is complex and FormMM can be called from many places. And since FormMM is global you can do this at any time - even after the user has closed the previous instance. In this case the FormMM variable is not valid any more! You may not even be aware that the form has been closed because the program is complex.

On the other hand, when you set the variable to nil upon closing you can always check "if FormMM <> nil then FormMM.DoSomething" before you do something with the form. It is similar to the "Active" and "MemoOpen" status variables, but a life saver when you attempt to access the form after it has been closed.

An easy way to set FormMM to nil is possible when FormMM is allowed to exist only once. In this case you can declare FormMM in the LCL_lib unit where its class TFormMM is implemented (in fact, that's where it belongs to). Simply use the OnDestroy handler (or better: override the Destroy destructor) and set FormMM to nil - this is possible now, because FormMM is a variable of this unit. Disadvantages are that you must remove the FormMM parameter from the Init_Memo and Free_Memo procedures.

I am attaching a modified version of you program which follows this idea.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 13, 2020, 10:01:08 am
Thank you so much, wp, for your new answer with many new details and your demo.

I had a deep look into your demo and using 1 global variable 'FormMM' seems to be a problem: as I wrote in reply #24, (currently only) one of my (older) programs uses 2 different Instances of the Memo-Form, which can be open at the same time. From my knowledge this can't work with 1 global variable 'FormMM'?
And in my new program I will use a couple of dynamic Memo-Forms for different purposes (most of them in a very simple way - there is only the one with the Access Violation, which is complex) and I'm not sure, if realy never more then 1 Memo-Form could be open at the same time.
A restriction to max. 1 open Memo-Form at the same time would be a great disadvantage to this common class, which I found very useful and expect to use in more programs in the future.

For a moment I thought, whether it could be possible, to use this class in my new program only for the complex usage with a global variable and keep all other usages of this class in this program and all other programs using a local variable as before, what normally would be possible with a class. But I saw, that you access variable 'FormMM' directly in TFormMM.Destroy() and Init_Memo() and Free_Memo(). From my knowledge it should work to use Init_Memo() and Free_Memo() again in their original form (with passing 'FormMM' as a var parameter). But for TFormMM.Destroy() this wouldn't work.

But again, I'm not (yet) convinced, that setting FormMM to nil is realy neccessary: of course you are right, that I may not access variable FormMM after it was closed. I checked my new program and at every place, where FormMM is accessed, I already wrote e.g.:
   if memoopen then FormMM.MM.Lines.Add(...);
Until now I counted 6 checks like this in 4 different procedures, which are all controlled by a "main procedure".

I did not mention before, that I already use those checks (because for me they were obvious), maybe this is the (only) reason why setting FormMM to nil is (was?) so important for you? 
Until now I see no difference, whether I have to check everywhere 'if memoopen' or 'if FormMM <> nil' (but the first would avoid a lot of effort - if this is not realy neccessary).

Do you agree?
Does this mean, that - as long as I don't forget to check 'memoopen' before accessing FormMM - that we can skip setting FormMM to nil and that I can solve my complete problem by deleting only the FreeAndNil() in free_Memo() - or deleting free_Memo() everywhere?

Thanks again a lot for your valuable help.
Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 13, 2020, 10:40:28 am
You don't need FormMM:= Nil at all if you don't like to use ASSIGNED. Instead you can use a global variable of course. If your program structure is better with those global variables is another question... The whole structure is not optimal if you ask me, but it's your program....

There is only one rule you have to follow: Don't free things more than once !  :D

by the way: the easiest way would be: create every window with (Application) and use Form.SHOW and Form.HIDE and when the program ends then let the APP free everything! That's very safe!
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 13, 2020, 02:49:23 pm
Thank you RAW for this new reply.

You don't need FormMM:= Nil at all if you don't like to use ASSIGNED. Instead you can use a global variable of course.
I'm happy that you see no reason, why checking my global variable wouldn't work. I hope, that wp will say the same...

Quote
The whole structure is not optimal if you ask me, but it's your program....
I agree. So I will not recommend to print this class in a book for "Best programming examples" ;-)

Quote
There is only one rule you have to follow: Don't free things more than once !  :D
Of course. And thanks to wp we now know, why CloseMM() does react so different and how we can react.

Quote
by the way: the easiest way would be: create every window with (Application) and use Form.SHOW and Form.HIDE and when the program ends then let the APP free everything! That's very safe!
Yes, wp suggested quasi the same in reply #18 and I answered:

If I would need this dynamic Memo-Form only in my new program, I think this would be the best (simpliest) way. But because this common class is used in various programs (and can do much more than I showed in this minimalized demo) it looks much easier to only empty the free_memo()
Title: Re: Access violation when freeing a dynamically created Form
Post by: wp on June 13, 2020, 04:12:37 pm
as long as I don't forget to check 'memoopen' before accessing FormMM
It may even be that your controlling mechanism with "memoOpen" will not even notice that a new form has been opened. If I understand your code correctly you show FormMM by calling its method "Show_Memo()" which changes the internal "pActive" parameter and which, together with "memoOpen", is the inidcator that the form is still alive. What do you think will happen when you open the form with "Show" - this does not touch "pActive" and bypasses your mechanism? In essence, you can achieve the same with FormMM, but in a more reliable way - you only must make sure that it becomes nil when the form is destroyed; this is the same as resetting "memoOpen" but the latter is less reliable because the related mechanism can be bypassed.

I am attaching another demo, this time with two instances of TFormMM which are allowed to exist simultaneously. The main form runs a timer which checks which of the two forms is currently open. In this case the main form needs to know the instances of TFormMM and it must know whether they are closed or not. In the current approach of destroying a form when it is closed the latter information is provided by the form variable being nil or not. For this to happen the program must be able to set the form variables to nil. In this demo the main form sets the OnClose handler of TFormMM and thus is able to set the related form variable to nil. If such a precaution would not have been made the program would crash when the timer event handler accesses the form variable although the form already might have been closed and destroyed.

I understand that you refrain from changing the code used by many projects. Of course, you can keep everything as it is and only empty the Free_Memo procedure. But it is a time-bomb, and since you plan to use this also in future projects you should fix it. You could create a new version of your LCL_lib and put the improved code in there. If you name the new version LCL_lib2 and use it only in the new projects you can keep the old projects untouched. Slowly, over time, you could adapt the old projects one by one by replacing unit LCL_lib by LCL_lib2. After the last old project has been migrated you can delete the outdated LCL_lib version from your code base.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 13, 2020, 10:05:17 pm
Hello wp, thanks again for your new reply and your new demo and the time you spent to help me.

Again I had a deep look into your demo and ran it a couple of times. Again I am impressed, that you found a solution for the "only 1 global variable" problem. It works.

Please let us compare the advantages of this new solution with the "old solution" = only deleting the FreeAndNil() in free_Memo(): If we look at this list, please understand, that the only benefit, that FormMM would be nil after usage, in my eyes is a "nice to have", yes, but much smaller than all the disadvantages, which I see. I don't want to pay this high price for this small benefit.

Of course might come the day, where I want to add a feature to this class, which is not possible with the current structure. At this day (if it comes) I must bite in this apple and must change those things, which are then neccessary - or which make sense at this time, because I already have to adapt (and test) all using programs.

So I decided to choose now the easy solution, to only delete FreeAndNil() in free_Memo(), because no one contradicted, that this would solve my Access violation problem.
 
Again many, many thanks to all who helped me with this difficult problem. Especially to wp, who was in the 1st line and who found out, where and why this problem was caused in the first place. Again I learned a lot from you.
 
This is a great forum.

Hartmut
Title: Re: Access violation when freeing a dynamically created Form
Post by: RAW on June 14, 2020, 12:51:43 am
hmmm funny:
Code: Pascal  [Select][+][-]
  1. // OnClose:
  2. CloseAction:= caFree;
  3. Form:= Nil;
  4.  
  5. // looks the same to me ...
  6. Form.Release;
  7. Form:= Nil;
  8.  
As I said before, that's the best way to close any form ...  :)



Quote
in both cases I must check a gloabl variable before accessing FormMM (either if FormMM <> nil or if memoopen is True). I see there no difference.
That's very wrong: your variable is only good for one thing and FormMM can do alot more... for example: If you like to access the TMemo you need the public variable FormMM to do that. With FormMM you can check if the form is Nil and access buttons, memos .. or whatever.

Quote
if the main program already has a FormClose-Event - or if I need to add one later - I have to separate both
The only thing you have to do is to add three lines of code to the existing OnClose event or create one for the main form.

Quote
as I wrote above, my new program will use a couple of dynamic Memo-Forms. For each Memo-Form which I add, peace by peace, I must additionally add it in the FormClose-Event. And what if I forget this?
This sounds alot like a design problem, choose another different design ...  :D

Quote
...this the current class in my eyes does a good job, while the new solution adds a couple of additional effort and risks in every program, which wants to use this class.
an excellent "Access-Violation"-job ...  :)

Quote
I would have to adapt all existing programs, at multiple places (either now or later - the sum wouldn't become smaller) including a higher effort for testing - for each program
Yeah, that's the way it is, if the structure is bad, but after that, all programs are running safe...
New program - new (better) structure is a very normal thing in the programming world, because every programmer gets better (or at least it should be like that).

Again: your program/problem -  your choice ...



Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 14, 2020, 10:22:53 am
Code: Pascal  [Select][+][-]
  1. // OnClose:
  2. CloseAction:= caFree;
  3. Form:= Nil;
  4.  
  5. // looks the same to me ...
  6. Form.Release;
  7. Form:= Nil;
  8.  
As I said before, that's the best way to close any form ...  :)
As I said two times before (see reply #3 and #14): with 'Form.Release' I still got an Access Violation Error. It did not help. Did you read them?

Quote
Quote
in both cases I must check a gloabl variable before accessing FormMM (either if FormMM <> nil or if memoopen is True). I see there no difference.
That's very wrong: your variable is only good for one thing and FormMM can do alot more... for example: If you like to access the TMemo you need the public variable FormMM to do that. With FormMM you can check if the form is Nil and access buttons, memos .. or whatever.
Where is a real difference between
   if FormMM <> nil then FormMM.MM.Lines.Add(...);
and   
   if memoopen then FormMM.MM.Lines.Add(...);
I don't see one.

Quote
Quote
as I wrote above, my new program will use a couple of dynamic Memo-Forms. For each Memo-Form which I add, peace by peace, I must additionally add it in the FormClose-Event. And what if I forget this?
This sounds alot like a design problem, choose another different design ...  :D
This is a disadvantage of the new solution. If you think, this is a design problem, then it's one in the new solution, not in my "old" class (because there this extra effort and extra risk is not neccessary).

Quote
Quote
...this the current class in my eyes does a good job, while the new solution adds a couple of additional effort and risks in every program, which wants to use this class.
an excellent "Access-Violation"-job ...  :)
There was one bug. Yes. Because I did not know about the peril of Form.Close() which wp found out (and seems that even he didn't know before). Now this bug is solved. Why throw the child out with the bath? (don't know if this German slogan is understandable after translating it into English)

Quote
Quote
I would have to adapt all existing programs, at multiple places (either now or later - the sum wouldn't become smaller) including a higher effort for testing - for each program
Yeah, that's the way it is, if the structure is bad, but after that, all programs are running safe...
New program - new (better) structure is a very normal thing in the programming world, because every programmer gets better (or at least it should be like that).
As said many times before: I see no need for such a huge effort (and such a long list of disadvantages, see reply #31) now.

Of course might come the day, where I want to add a feature to this class, which is not possible with the current structure. At this day (if it comes) I must bite in this apple and must change those things, which are then neccessary - or which make sense at this time, because I already have to adapt (and test) all using programs.

Quote
Again: your program/problem -  your choice ...
Yes. Please respect this.
Title: Re: Access violation when freeing a dynamically created Form
Post by: Hartmut on June 14, 2020, 10:41:53 am
Because this Topic got very long here is a short summary:

How / where did the problem (which leaded to an Access violation) occur in the first place?
 - when ABORT-Button is not pressed, FormMM.Close() is called in line 46 of Unit1. FormMM.Close calls ReleaseComponent, which does not destroy the form immediately, but drops the request for it in the message queue.
 - when ABORT-Button is pressed, FormMM.Close() is called in line 57 of unit LCL_lib. Again FormMM.Close does the same as before. But now in the calling procedure (TForm1.ButtonClick) there is in line 40 of Unit1 an Application.ProcessMessages, which handles all commands accumulated in the message queue - among them the request to destroy FormMM.
So in the 1st case, when free_Memo() is called in line 48 of Unit1, the request to destroy FormMM has not yet executed and no Access violation occurs.
But in the 2nd case, when the same free_Memo() is called, the request to destroy FormMM has been already executed (because of Application.ProcessMessages) and the Access violation occurs.
This was found out by wp. Thanks to him again.

How does the solution work, which I choosed?
Because of 'CloseAction:=caFree' in line 67 of unit LCL_lib the FreeAndNil() in free_Memo() = line 80 of unit LCL_lib is not neccessary, because "the form will be destroyed automatically whenever the user closes the form or when FormMM.Close is called". So I only deleted this one line - that's all :-)

Addendum: meanwhile I could solve the 2 Heaptrc-problems (see end of reply #9) and made a couple of tests and the results were always "everything ok" :-)

What about an improvement and alternative solutions?
This solution has a (in my eyes not very big) disadvantage, because FormMM is not set to nil after usage. This is not very "clean". And you cannot do things like 
   if FormMM <> nil then FormMM.MM.Lines.Add(...);
But fortunately my old class 'TFormMM' already provides a global control variable (in this demo called 'memoopen'), which can do exactly the same job by
   if memoopen then FormMM.MM.Lines.Add(...);
   
There have been a couple of different attempts by wp, to set automatically FormMM to nil after usage. But this was quite difficult to achieve and had (in my eyes) a long list of disadvantages (see reply #31) like much additional effort and nameable more complexity and less flexibility, how this common class could be used everywhere.

So I decided to choose now the much easier solution.

Again many thanks to all who helped me with this difficult problem. I learned a lot.
Title: Re: [SOLVED] Access violation when freeing a dynamically created Form
Post by: RAW on June 14, 2020, 12:59:22 pm
Quote
Where is a real difference between
   if FormMM <> nil then FormMM.MM.Lines.Add(...);
and   
   if memoopen then FormMM.MM.Lines.Add(...);
I don't see one.
Easy: the second way needs 2 global variables and the first way only one, so the first way is less complex and easier and safer ... !!!
Also the boolean "memoopen" needs to be set to false or true exactly as the NIL-way and so that's a  bad design!
Quote
if pActiv <> nil then pActiv^:=false; {reset a flag that the Form is closed}

by the way: the demo program is working very fine if you choose to delete "CloseAction:=caFree;" or "FreeAndNil(FormMM);"!!!
The whole thing is all about destroying a form that is already destroyed... that's it... very easy to see... no big deal!
Title: Re: [SOLVED] Access violation when freeing a dynamically created Form
Post by: Hartmut on June 14, 2020, 02:41:50 pm
Hello RAW,
why do you continue to argue, after the decision has fallen? Can't you accept / respect this?

Quote
Where is a real difference between
   if FormMM <> nil then FormMM.MM.Lines.Add(...);
and   
   if memoopen then FormMM.MM.Lines.Add(...);
I don't see one.
Easy: the second way needs 2 global variables and the first way only one, so the first way is less complex and easier and safer ... !!!
The difference is one variable, which needs 1 byte of memory, in a program of 10 or 20 MB or more. In my eyes this is nothing. I would not talk of "more complexity" because of something like this.

Quote
Also the boolean "memoopen" needs to be set to false or true exactly as the NIL-way and so that's a  bad design!
But the big difference is:
 - memoopen is set automatically inside the class and it is absolute easy to do this
 - setting FormMM to nil requires a huge effort and additional it increases the complexity and reduces the flexibility, how this common class could be used everywhere.
For this reasons, I prefer the "design" of my old class.

Obviously we have different opinions about what counts more in the question, what - in sum - is a "better" design or not. This should not be a problem... Fortunately it's my job to decide, which solution I'll use.

Quote
by the way: the demo program is working very fine if you choose to delete "CloseAction:=caFree;" or "FreeAndNil(FormMM);"!!!
The whole thing is all about destroying a form that is already destroyed...
Did you read nothing of this Topic?
1) Why "CloseAction:=caFree" is neccessary was written multiple times!
2) And many people have already written uncountable times that to delete FreeAndNil(FormMM) is a solution! Why dou you repeat things, that we all know for long?

I don't want to continue this discussion, it makes no sense.
Title: Re: Access violation when freeing a dynamically created Form
Post by: ASBzone on June 14, 2020, 06:15:10 pm
Why throw the child out with the bath? (don't know if this German slogan is understandable after translating it into English)

The English version of this idiom is "Why throw out the child/baby with the bath water?"

Close enough.  :D
Title: Re: [SOLVED] Access violation when freeing a dynamically created Form
Post by: Hartmut on June 14, 2020, 07:09:10 pm
Thanks, ASBzone.
Title: Re: [SOLVED] Access violation when freeing a dynamically created Form
Post by: RAW on June 14, 2020, 08:01:32 pm
I don't argue at all, I put things right! A bad structure is not an opinion ...

Quote
Again many thanks to all who helped me with this difficult problem. I learned a lot.
You made a careless mistake. You destroyed a already destroyed form. That is not a problem or a difficult problem at all. It's just a careless mistake. It's not hard to close or destroy a window and if you do it twice, then of course it won't work. It has nothing to do with RELEASE or FREE or FREEANDNIL.

Quote
And from my programming experience (42 years next month, with some gaps)
You declared that you have 42 years programming "experience" and then you talk about things that are totally wrong, so there must be someone who put this right, especially in a forum where a lot of new programmers are around and maybe like to copy your structure because of your declared "experience"...

Quote
The difference is one variable, which needs 1 byte of memory, in a program of 10 or 20 MB or more. In my eyes this is nothing. I would not talk of "more complexity" because of something like this.
A variable that is not necessary is a bad variable and can cause to further careless mistakes.

Quote
- memoopen is set automatically inside the class and it is absolute easy to do this
It doesn't matter if it's easy, it's not necessary. Even this simple and funny FUNCTION can handle it without any global variable:
Code: Pascal  [Select][+][-]
  1. Function WndExists(Wnd:TForm):Boolean;
  2. Begin
  3.   Result:= False;
  4.   Try
  5.    Wnd.Caption;
  6.    Result:= True;
  7.   Except
  8.   End;
  9. End;
  10.  
  11. // outside the IDE this works fine
  12. // inside it produces an exception anyway if Wnd is destroyed

Quote
- setting FormMM to nil requires a huge effort and additional it increases the complexity and reduces the flexibility, how this common class could be used everywhere.
Setting FormMM:= Nil inside OnClose isn't a huge effort and that it would reduce the flexibility is wrong too. The opposite is true, the flexibility is extended because now you can use ASSIGNED if you want and if not it changes nothing...


Just to make things clear !
TinyPortal © 2005-2018