Recent

Author Topic: [SOLVED] Access violation when freeing a dynamically created Form  (Read 14467 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
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:
  • The dynamic Form can be showed modal or not modal. If not modal, the user e.g. can open the Memo-Form via a Button and keep it open as long as he wants while he can continue working with the main program.
  • To prevent, that the same Memo-Form can be opened more than once, there was a Flag 'pActiv' introduced, which is reset to 'False' at the moment, where the User closes this Memo-Form. (But in this program here, this Flag instead is used to check, whether the ABORT-Button has been pressed or not).
  • Procedure init_Memo() uses 'Application' as parent, because then, if the main program terminates and the dynamic Memo-Form is still open, it is automatically closed (in this little demo this feature is not in use, but I need it in other programs).
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.
« Last Edit: June 14, 2020, 10:46:47 am by Hartmut »

RAW

  • Hero Member
  • *****
  • Posts: 871
Re: Access violation when freeing a dynamically created Form
« Reply #1 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 ???

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: Access violation when freeing a dynamically created Form
« Reply #2 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

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Re: Access violation when freeing a dynamically created Form
« Reply #3 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.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Re: Access violation when freeing a dynamically created Form
« Reply #4 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.

rvk

  • Hero Member
  • *****
  • Posts: 6885
Re: Access violation when freeing a dynamically created Form
« Reply #5 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.

wp

  • Hero Member
  • *****
  • Posts: 13195
Re: Access violation when freeing a dynamically created Form
« Reply #6 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.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Re: Access violation when freeing a dynamically created Form
« Reply #7 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?

rvk

  • Hero Member
  • *****
  • Posts: 6885
Re: Access violation when freeing a dynamically created Form
« Reply #8 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.

Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Re: Access violation when freeing a dynamically created Form
« Reply #9 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:
  • it was not possible to stop the program with it's Close-Button, because a loop ran endless when Heaptrc was active, while without Heaptrc it did not. I could fix it.
  • the "thousands" of EAccessViolations (on Linux only) occured only, because function system.get_cmdline() was called. Seems to be a bug in FPC 3.0.4, because in FPC 3.2.0-beta rev=43824 this problem does not occur. Sometimes I prefer system.get_cmdline() instead of ParamStr() because the last does several manipulations and can on Windows return different charsets (sometimes Windows-charset and sometimes UTF8).
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".
« Last Edit: June 18, 2020, 09:02:19 am by Hartmut »

RAW

  • Hero Member
  • *****
  • Posts: 871
Re: Access violation when freeing a dynamically created Form
« Reply #10 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...

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: Access violation when freeing a dynamically created Form
« Reply #11 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

RAW

  • Hero Member
  • *****
  • Posts: 871
Re: Access violation when freeing a dynamically created Form
« Reply #12 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...  :)

wp

  • Hero Member
  • *****
  • Posts: 13195
Re: Access violation when freeing a dynamically created Form
« Reply #13 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.



Hartmut

  • Hero Member
  • *****
  • Posts: 1000
Re: Access violation when freeing a dynamically created Form
« Reply #14 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?

 

TinyPortal © 2005-2018