Recent

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

Hartmut

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

Hartmut

  • Hero Member
  • *****
  • Posts: 1061
Re: Access violation when freeing a dynamically created Form
« Reply #16 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.
  • At first I only needed a dynamic modal Memo to display something until the Memo was closed.
  • Later I improved it, so that it can work not modal too. Therefore I had to add Flag 'pActiv' to be able to control, that the same Memo-Instance can't be opened more than once at the same time (e.g. the User has a Button or a Hotkey to open the Memo, but this should only work, if a recently opened Memo had been closed before). And because each not modal Memo could stay open for a long time, it became neccessary, that when the main program terminates, that all still open Memos are closed at this moment too. Therefore I introduced 'CreateNew(Application)' in init_Memo() and 'CloseAction:=caFree' in CloseMM(). Until now everything worked perfectly in all programs.
  • Now I'm working on a new program and need a dynamic not modal Memo, but now I want to use the existing Button on the Memo-Form to abort a running task. I thought, that everything was perfectly prepared and ready to use for this case, because now I could use the already existing Flag 'pActiv' to check, whether the Abort-Button had been pressed or not. And everything works fine, except the Access violations in 2 of 3 situations (which I explained in reply #3).
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).

Bart

  • Hero Member
  • *****
  • Posts: 5674
    • Bart en Mariska's Webstek
Re: Access violation when freeing a dynamically created Form
« Reply #17 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
« Last Edit: June 11, 2020, 11:09:08 pm by Bart »

wp

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


RAW

  • Hero Member
  • *****
  • Posts: 871
Re: Access violation when freeing a dynamically created Form
« Reply #19 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.
« Last Edit: June 12, 2020, 03:57:16 pm by RAW »

Hartmut

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

wp

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

Hartmut

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

wp

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

Hartmut

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

Hartmut

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

wp

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

Hartmut

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

RAW

  • Hero Member
  • *****
  • Posts: 871
Re: Access violation when freeing a dynamically created Form
« Reply #28 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!
« Last Edit: June 13, 2020, 10:51:37 am by RAW »

Hartmut

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

 

TinyPortal © 2005-2018