What is the correct way to dynamically create/release runtime forms? - delphi

I always try to create my Applications with memory usage in mind, if you dont need it then don't create it is the way I look at it.
Anyway, take the following as an example:
Form2:= TForm2.Create(nil);
try
Form2.ShowModal;
finally
Form2.FreeOnRelease;
end;
I actually think Form2.Destroy is probably the better option, which brings me to my question..
What is the difference between calling:
Form2.Destroy;
Form2.Free;
Form2.FreeOnRelease;
They all do the same or similar job, unless I am missing something.
And also when should any of the above be used? Obviously when freeing an Object I understand that, but in some situations is Destroy better suited than Free for example?

Form2:= TForm2.Create(nil);
This is a code-smell, because Form2 is probably the global, IDE-generated variable that would normally hold an IDE-created TForm2. You most likely want to use a local variable, and one with a better name. This is not necessary an error, just a code-smell.
Form2.Destroy vs Form2.Free
Use Form2.Free, because it calls Destroy anyway. You can CTRL+Click on the name (Free) to see it's implementation. Essentially Free calls Destroy if Self is not nil.
Form2.FreeOnRelease
As the documentation says, "It should not be necessary to call FreeOnRelease directly."

I've never actually heard of FreeOnRelease before. A quick Google search turned up the reason why. From the official documentation:
FreeOnRelease is called when an
interface implemented by the component
is released. FreeOnRelease is used
internally and calls the corresponding
interface method. It should not be
necessary to call FreeOnRelease
directly.
As for Free vs. Destroy, Free is a safety feature. It's basically implemented as if self <> nil then self.Destroy;, and it was created to make constructors and destructors safe to use. Here's the basic idea:
If you're constructing an object and an unhandled exception is raised, the destructor gets called. If your object contains other objects, they may or may not have been created yet by the time the error occurred, so you can't just try to call Destroy on all of them. But you need a way to make sure that the ones that have been created do get destroyed.
Since Delphi zeros out the address space of an object before calling the constructor, anything that hasn't been created yet is guaranteed to be nil at this point. So you could say if FSubObject <> nil then FSubObject.Destroy again and again for all the sub-objects, (and if you forget that you're going to get access violations,) or you can use the Free method, which does it for you. (This is a huge improvement over C++, where the memory space is not zeroed before the constructor is called, which requires you to wrap all your sub-objects in smart pointers and use RAII to maintain exception safety!)
It's useful in other places as well, and there's really no reason not to use it. I've never noticed that Free imposes any measurable performance penalty, and it improves the safety of your code, so it's a good idea to use it in all cases.
Having said that, when dealing with forms specifically, there's an additional variable to factor into the equation: the Windows message queue. You don't know if there are still pending messages for the form you're about to free, so it's not always safe to call Free on a form. For that, there's the Release method. It posts a message to the queue that causes the form to free itself once it's got no more messages to handle, so it's generally the best way to free a form you no longer need.

The canonical form is:
Form := TMyForm.Create(nil);
try
Form.ShowModal;
finally
Form.Free;
end;
Never call Destroy, always call Free instead.
FreeOnRelease is a total red herring. Sometimes, if there are queued messages destined for your form or its children, then you might elect to call Release although often that's indicative of design problems.

The idiomatic usage is
procedure SomeProc;
var
frm: TForm2;
begin
frm := TForm2.Create(nil);
try
frm.ShowModal;
finally
frm.Free;
end;
end;
or, unless you hate the with construct,
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
You should never call Destroy, according to the documentation. In fact, Free is exactly equivalent to if Self <> nil then Destroy;. That is, it is a 'safe' version of Destroy. It doesn't crash totally if the pointer happens to be nil. [To test this, add a private field FBitmap: TBitmap to your form class, and then OnCreate (for instance), try FBitmap.Free vs. FBitmap.Destroy.]
If you create the form using the approach above, Free is perfectly safe, unless you do some strange things in the form class.
However, if you use CreateForm(TForm2, Form2) to create the form and store the form object in the global instance variable Form2 and you don't free it immediately [for instance, if you want the window to stick around next to the main form in a non-modal way for a few minutes], you should probably use Release instead of Free. From the documentation,
Release does not destroy the form
until all event handlers of the form
and event handlers of components on
the form have finished executing.
Release also guarantees that all
messages in the form's event queue are
processed before the form is released.
Any event handlers for the form or its
children should use Release instead of
Free (Delphi) or delete (C++). Failing
to do so can cause a memory access
error.
FreeOnRelease has nothing in particular do to with forms. From the docs:
It should not be necessary to call
FreeOnRelease directly.

the other way is passing caFree to Action of formonclose
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end

Related

Why is using procedures to create objects preferred over functions?

This is similar to this question. I asked "Why?" to the most popular response but I don't know that anyone would ever look at it again. At least not in any timely manner.
Anyway, my question is about best practices for delegating responsibility for creation of objects to functions or procedures, without causing memory leaks. It seems that this:
procedure FillObject(MyObject: TMyObject; SomeParam: Integer);
begin
//Database operations to fill object
end;
procedure CallUsingProcedure();
var
MyObject: TMyObject;
begin
MyObject = TMyObject.Create();
try
FillObject(MyObject, 1);
//use object
finally
MyObject.Free();
end;
end;
is preferred over this:
function CreateMyObject(DBID: Integer): TMyObject;
begin
Result := TMyObject.Create();
try
//Database operations to fill object
except on E: Exception do
begin
Result.Free();
raise;
end;
end;
end;
procedure CallUsingFunction();
var
MyObject: TMyObject;
begin
MyObject = CreateMyObject(1);
try
//use object
finally
MyObject.Free();
end;
end;
Why?
I'm relatively new to Delphi, having previously worked most with Java and PHP, as well as C++, though to a lesser extent. Intuitively, I lean toward the function method because:
It encapsulates the object creation code in the function, rather than create the object separately whenever I want to use the procedure.
I dislike methods that alter their parameters. It's often left undocumented and can make tracing bugs more difficult.
Vague, but admittedly it just "smells" bad to me.
I'm not saying I'm right. I just want to understand why the community chooses this method and if there is good reason for me to change.
Edit:
References to #E-Rock in comments are to me(Eric G). I changed my display name.
One problem is what Ken White wrote: you hand the user of the function an object he or she must free.
Another advantage of procedures is that you can pass several objects of a hierarchy, while a function that creates such an object always generates the same. E.g.
procedure PopulateStrings(Strings: TStrings);
To that procedure, you can pass any kind of TStrings, be it the Lines of a TMemo, the Items of a TListBox or TComboBox or a simple standalone TStringList. If you have a function:
function CreateStrings: TStrings;
You always get the same kind of object back (which object exactly is not known, as TStrings is abstract, so you probably get a TStringList), and must Assign() the contents to the TStrings you want to modify. The procedure is to be preferred, IMO.
Additionally, if you are the author of the function, you can't control whether the object you create is freed, or when. If you write a procedure, that problem is taken off your hands, since the user provides the object, and its lifetime is none of your concern. And you don't have to know the exact type of the object, it must just be of the class or a descendant of the parameter. IOW, it is also much better for the author of the function.
It is IMO seldom a good idea to return an object from a function, for all the reasons given. A procedure that only modifies the object has no dependency on the object and creates no dependency for the user.
FWIW, Another problem is if you do that from a DLL. The object returned uses the memory manager of the DLL, and also the VMT to which it points is in the DLL. That means that code that uses as or is in the user code does not work properly (since is and as use the VMT pointer to check for class identity). If the user must pass an object of his, to a procedure, that problem does not arise.
Update
As others commented, passing an object to a DLL is not a good idea either. Non-virtual functions will call the functions inside the DLL and use its memory manager, which can cause troubles too. And is and as will not work properly inside the DLL either. So simply don't pass objects into or out of a DLL. That goes with the maxime that DLLs should only use POD type parameters (or compound types -- arrays, records -- that only contain POD types) or COM interfaces. The COM interfaces should also only use the same kind of parameters.
Creating the object instance and passing it into another procedure makes it clear which code is responsible for freeing the instance.
In the first case (using a procedure to fill it):
MyObj := TMyObject.Create;
try
// Do whatever with MyObj
finally
MyObj.Free;
end;
This is clear that this block of code is responsible for freeing MyObj when it's finished being used.
MyObj := CreateMyObject(DBID);
What code is supposed to free it? When can you safely free it? Who is responsible for exception handling? How do you know (as a user of someone else's code)?
As a general rule, you should create, use, and free object instances where they're needed. This makes your code easier to maintain, and definitely makes it easier for someone who comes along later and has to try and figure it out. :)
I use a combination of both idioms. Pass the object as an optional parameter and if not passed, create the object. And in either case return the object as the function result.
This technique has (1) the flexibility of the creation of the object inside of the called function, and (2) the caller control of the caller passing the object as a parameter. Control in two meanings: control in the real type of the object being used, and control about the moment when to free the object.
This simple piece of code exemplifies this idiom.
function MakeList(aList:TStrings = nil):TStrings;
var s:TStrings;
begin
s:=aList;
if s=nil then
s:=TSTringList.Create;
s.Add('Adam');
s.Add('Eva');
result:=s;
end;
And here are three different ways to use it
simplest usage, for quick and dirty code
var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
when programmer wants to make more explicit ownership and/or use a custom type
sl2:=MakeList(TMyStringsList.create);
when the object is previously created
sl3:=TMyStringList.Create;
....
MakeList(sl3);

How to debug a (possible) RTL problem?

I'm asking this because I'm out of good ideas...hoping for someone else's fresh perspective.
I have a user running our 32-bit Delphi application (compiled with BDS 2006) on a Windows 7 64-bit system. Our software was "working fine" until a couple weeks ago. Now suddenly it isn't: it throws an Access Violation while initializing (instancing objects).
We've had him reinstall all our software--starting all over from scratch. Same AV error. We disabled his anti-virus software; same error.
Our stack tracing code (madExcept) for some reason wasn't able to provide a stack trace to the line of the error, so we've sent a couple error logging versions for the user to install and run, to isolate the line which generates the error...
Turns out, it's a line which instances a simple TStringList descendant (there's no overridden Create constructor, etc.--basically the Create is just instancing a TStringList which has a few custom methods associated with the descendant class.)
I'm tempted to send the user yet another test .EXE; one which just instances a plain-vanilla TStringList, to see what happens. But at this point I feel like I'm flailing at windmills, and risk wearing out the user's patience if I send too many more "things to try".
Any fresh ideas on a better approach to debugging this user's problem? (I don't like bailing out on a user's problems...those tend to be the ones which, if ignored, suddenly become an epidemic that 5 other users suddenly "find".)
EDIT, as Lasse requested:
procedure T_fmMain.AfterConstruction;
begin
inherited;
//Logging shows that we return from the Inherited call above,
//then AV in the following line...
FActionList := TAActionList.Create;
...other code here...
end;
And here's the definition of the object being created...
type
TAActionList = class(TStringList)
private
FShadowList: TStringList; //UPPERCASE shadow list
FIsDataLoaded : boolean;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
procedure DataLoaded;
function Add(const S: string): Integer; override;
procedure Delete(Index : integer); override;
function IndexOf(const S : string) : Integer; override;
end;
implementation
procedure TAActionList.AfterConstruction;
begin
Sorted := False; //until we're done loading
FShadowList := TStringList.Create;
end;
I hate these kind of problems, but I reckon you should focus on what's happening recently BEFORE the object tries to get constructed.
The symptoms you describe sound like typical heap corruption, so maybe you have something like...
An array being written to outside bounds? (turn bounds checking on, if you have it off)
Code trying to access an object which has been deleted?
Since my answer above, you've posted code snippets. This does raise a couple of possible issues that I can see.
a: AfterConstruction vs. modified constructor:
As others have mentioned, using AfterConstruction in this way is at best not idiomatic. I don't think it's truly "wrong", but it's a possible smell. There's a good intro to these methods on Dr. Bob's site here.
b: overridden methods Add, Delete, IndexOf
I'm guessing these methods use the FshadowList item in some way. Is it remotely possible that these methods are being invoked (and thus using FShadowList) before the FShadowList is created? This seems possible because you're using the AfterConstruction methods above, by which time virtual methods should 'work'. Hopefully this is easy to check with a debugger by setting some breakpoints and seeing the order they get hit in.
You should never override AfterConstruction and BeforeDestruction methods in your programs. They are not meant for what you're doing with them, but for low-level VCL hacking (like reference adding, custom memory handling or such).
You should override the Create constructor and Destroy destructor instead and put your initialization code here, like such:
constructor TAActionList.Create;
begin
inherited;
// Sorted := False; // not necessary IMHO
FShadowList := TStringList.Create;
end;
Take a look at the VCL code, and all serious published Delphi code, and you'll see that AfterConstruction and BeforeDestruction methods are never used. I guess this is the root cause of your problem, and your code must be modified in consequence. It could be even worse in future version of Delphi.
Clearly there is nothing suspicious about what TAActionList is doing at time of construction. Even considering ancestor constructors and possible side-effects of setting Sorted := False indicate there shouldn't be a problem. I'm more interested in what's happening inside T_fmMain.
Basically something is happening that causes FActionList := TAActionList.Create; to fail, even though there is nothing wrong in the implementation of TAActionList.Create (a possibility is that the form may have been unexpectedly destroyed).
I suggest you try changing T_fmMain.AfterConstruction as follows:
procedure T_fmMain.AfterConstruction;
begin
//This is safe because the object created has no form dependencies
//that might otherwise need to be initialised first.
FActionList := TAActionList.Create;
//Now, if the ancestor's AfterConstruction is causing the problem,
//the above line will work fine, and...
inherited AfterConstruction;
//... your error will have shifted to one of these lines here.
//other code here
end;
If an environment issue with a component used by your form is causing it destroy the form during AfterConstruction, then it's the assignment of the new TAActionList.Create instance to FActionList that's actually causing the AV. Another way to test would be to first create the object to a local variable, then assign it to the class field: FActionList := LActionList.
Environment problems can be subtle. E.g. We use a reporting component which we discovered requires that a printer driver is installed, otherwise it prevents our application from starting up.
You can confirm the destruction theory by setting a global variable in the form's destructor. Also you may be able to output a stack trace from the destructor to confirm the exact sequence leading to the destruction of the form.
Our software was "working fine" until a couple weeks ago... suddenly become an epidemic that 5 other users suddenly "find".) :
Sounds like you need to do some forensic analysis, not debugging: You need to discover what changed in that user's environment to trigger the error. All the more so if you have other users with the same deployment that don't have the problem (sounds like that's your situation). Sending a user 'things to try' is one of the best ways to erode user confidence very quickly! (If there is IT support at the user site, get them involved, not the user).
For starters, explore these options:
*) If possible, I'd check the Windows Event Log for events that may have occurred on that machine around the time the problem arose.
*) Is there some kind of IT support person on the user's side that you can talk to about possible changes/problems in that user's environment?
*) Was there some kind of support issue/incident with that user around the time the error surfaced that may be connected to it, and/or caused some kind of data or file corruption particular to them?
(As for the code itself, I agree with #Warran P about decoupling etc)
Things to do when MadExcept is NOT Enough (which is rare, I must say):
Try Jedi JCL's JCLDEBUG instead. You might get a stack traceback with it, if you change out MadExcept for JCLDEBUG, and write directly the stack trace to the disk without ANY UI interaction.
Run a debug-viewer like MS/SysInternals debugview, and trace output things like the Self pointers of the objects where the problems are happening. I suspect that somehow an INVALID instance pointer is ending up in there.
Decouple things and refactor things, and write unit tests, until you find the really ugly thing that's trashing you. (Someone suggested heap corruption. I often find heap corruption goes hand in hand with unsafe ugly untested code, and deeply bound UI+model cascading failures.)

How do I check if form is Closed?

The only way I see is to add flag for this, but is this the best way?
When the form is destroyed and I check if(Assigned(form2)) the result is true? Why?
What is the way to do this?
You can use Form1.Showing to see if a form is closed or not.
Just closing a form does not free it unless you set Action := caFree in OnClose event. Default is caHide.
Wow, a blast from the past :)
The way that Assigned() works, is that it basically does nil check on the pointer. If you destroy form2, there will still be a memory address that form2 points to.
I has been a very long time since I've done any Delphi, but from memory, you need to manually set the form2 var to nil when it is destroyed. If you have a central place (eg. a form broker?) where you create & destroy forms, this should be quite easy.
If you use Form1.Free or Form1.Destroy, Delphi will destroy the object but wont set the object reference to nil. So instead use FreeAndNil.
For more information, check Andreas Rejbrand answer in this link
Faced with the same issue when doing some routine on closing application. In this case all forms are destroyed behind the stage but pointers are not set to nil. This code helphs me:
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyForm:=nil;
end;
So pointer becomes nil and I can check it with Assigned or compare to nil.
Just as a tip, the correct way to do it in some special cases is to create a timer that do the nil assign to the variable.
I will explain it (somehow it is complex), if you create your form within your own code MyForm:=TMyForm.Create and you have a MyFrom.Close it is very easy, just add a MyForm:=nil or also better MyForm.FreeAndNil... but sometimes the reference is not anyware.
Sample: You create inside a procedure, on a loop a lot of copies of the same form (or just one), let the form open and end that procedure, now the reference to the opened form is nowhere, so you can not assign nil or do a freeandnil, etc., in a normal way.
For that cases, the trick is to use a timer (of just one milisecond) that do it, that timer needs the reference, so you must store on a global like the reference to Self, all that can be done on the on close event.
The easiest way to do the free (when no reference anywhere) is to create a TObjectList on the main form, so it will hold all form references that needs to be free, and define a timer (one milisecond) that will go through that list doing the freeandnil; then on the onlcose you add Self to that list and enable that timer.
Now the other part, you have a normal form that is auto created on start, but you need to set it to nil and re-create it on your own code.
That case has a global that point to that form, so you only need to free and nil it, but NOT (i say it loud) on any part inside the own form code, you must do it OUT (i say if loud) of the form code.
Some times you will need to free the form, when user close it, and it is not shown in modal, this case is complex, but again the same trick is valid, on the onclose event you enable a timer (that is out of that form, normally on main form) adn that timer will free and nil. That timer interval can be set as just one milisecond, it will not be run until form has totally closed (please have in mind not using Application.ProcessMessages, that is normally a really bad idea).
If you set Self to nil, free or whatever inside the own form, you can corrupt your application memory (doing that is totally unsafe, not to mention it can eat ram).
The only way to free a form (and nil its reference), a form that is not shown as modal and is the user who close it, is to program a trigger that do that after the form is totally closed.
I know about setting action to do the free, but to set it to nil there is no other safe way.
Must say: If you use timers on your main form, run a Enabled:=False on all of them on the Onclose event... otherwise weird things may occur (not allways, but sometimes... race conditions about destroying application and running code on that timers), and of couse if some one was enabled act correctly to terminate it correctly or abort it, etc.
Your question is one of the complex things to do... free and nil a form that is closed not by code, but by user action.
For all the rest: Think like if the application has at the same time a lot of forms opened and all can interact with the user at the same time (anyone is modal), and you have code that references some of them from the others... you need to know f user has closed any form to avoid accesing that form from code. This is not trivial to be done unless you use timers.
If you have a 'central' form (like an MDI application) you can put that timer on the main MDI form, so any child form that is closed can be freed and nil, the trick is again a timer on that main form.
Only and only if you are sure you can free and nil all non visible forms, you can have a timer on the main form that goes through all forms and if Visible is false, then call FreeAndNil, i consider this way prone to errors, since if you add on a future a form that must not be freed but can stay hidden... this code will not be valid.
Allways remember that if is the user the onw who closes the form that must be freed and nil, there is no way on code to detect and act, no event is launched (after the form is totally closed) and before the form is totally closed you must not even try to free it or nil its reference, weird things can occur (more prone to that if motherboard has more than one socket, also more if your app uses threads, etc).
So, for threaded apps (and also not threaded) i use another method that works great and do not need timers, but need double checking prior to each ThatForm.*, the trick is to define a Form bolean public variable like as PleaseFreeAndNilMe on the public section on the form, then on the onclose (as last line) set it to True and on the OnCreate set it as False.
That way you will know if that form had been closed or only hidden (to hide a form never call close, just call hide).
So coded will look like (you can use this as a warper, instead of defining forms as TForm define them as TMyform, or also better, use a hack like type TForm=class(Forms.TForm) instead of TMyForm=class(TForm) just to have that variable added to all forms):
TMyForm=class(TForm)
...
public
PleaseFreeAndNilMe:=Boolean;
...
procedure TMyForm.FormCreate(Sender: TObject);
begin
PleaseFreeAndNilMe:=False;
...
end;
procedure TMyForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
...
PleaseFreeAndNilMe:=True;
end;
If you preffer hacked version:
TForm=class(Froms.TForm)
public
PleaseFreeAndNilMe:=Boolean;
end;
procedure TForm.FormCreate(Sender:TObject);
begin
inherited Create(Sender);
PleaseFreeAndNilMe:=False;
end;
procedure TForm.FormClose(Sender:TObject;var Action:TCloseAction);
begin
PleaseFreeAndNilMe:=True;
inherited FormClose(Sender,Action);
end;
But as i said, prior to access any member (or just where you do the nil compare) just call a 'global' function passign the reference (no matter if it was nil or not), coded as:
function IsNilTheForm(var TheForm: TMyForm);
begin
if nil=TheForm
then begin // The form was freed and nil
IsNilTheForm:=True; // Return value
end
else begin // The form refence is not nil, but we do not know is it has been freed or not
try
if TheForm.PleaseFreeAndNilMe
then begin // The form is not freed but wants to
try
TheForm.Free;
except
end;
try
TheForm:=Nil;
except
end;
IsNilTheForm:=True; // Return value
end
else begin // The form is not nil, not freed and do not want to be freed
IsNilTheForm:=False; // Return value
end;
except // The form was freed but not set to nil
TheForm:=Nil; // Set it to nil since it had beed freed
IsNilTheForm:=True; // Return value
end;
end;
end;
So where you do if nil=MyForm then ... you can now do if (IsNilTheForm(MyForm)) then ....
That is it.
It is better the timer solution, since form is freed as soon as possible (less ram used), with the PleaseFreeAndNilMe trick the form is not freed until IsNilTheForm is called (if you do not free it any where else).
That IsNilTheForm is so complex because it is considering all states (for a multi socket motherboard and threaded apps) and letting the code free / nil it anywhere else.
Of course, that function must be called on main thread and in atomic exclusion.
Free a Form and Nil its pointer is not a trivial thing, most when user can close it at any time (since no code out of the form is fired).
The big problem is: When a user closes a form there is no way to have a event handler that is triggered out of that form and after the form ends all things it is doing.
Imaigne now that the coder has put a lot of Application.ProcessMessages; every where on the app, also on that form, etc... and has not taken the care for race conditions... try to free and nil such a form after the user asks it to be closed... this is a nightmare, but can be solved with the hacked version of TForm that has a variable that tells that the form has not been freed but wants it.
Now imagine you use hacked TForm and want a normal TForm, just define it as ...= class(Forms.TForm), that way it will now have that extra variable., so calling IsNilTheForm will act as comparing to nil.
Hope this helps VCL coders to FIX such things, like raising an event when an object is destroyed, freed, niled, hide, etc... out of the code of that object, like on main form, etc. That would make live easier... or just fix it... Close and Free implies set to Nil all refences that point to it.
There is another thing that can be done (but i try to allways avoid it): Have multiple variables that point to the exact same form (not to copies ot it), that is prone to a lot of errors, you free one and need to nil all of them, etc... The code i show is also compatible with that.
I know the code is comples... but Free and Nil a form is more complex than my code.

Flipping through two forms

I am just wondering if I'm doign something that might be bad, although it seems a very practical solution to me...
I have two forms which the user will have to walk through. The user clicks on a button and form1 pops up. The user presses OK and the second one pops up. The user click OK again and the screens are gone. Or the user clicks on Retry and the screen goes back to the first one. The two screens are completely different sizes with different information.
So I came up with this code:
Form1 := TForm1.Create(SharedData);
Form2 := TForm2.Create(SharedData);
repeat
ModalResult := Form1.ShowModal;
if (ModalResult = mrOK) then ModalResult := Form2.ShowModal;
until (ModalResult <> mrRetry);
Form1.Release;
Form2.Release;
I've tested this code and it seems to work like a charm. In this code, SharedData is an object that contains data that's manipulated by both forms. I create this object before the two forms are created and when ModalResult==mrOK I just write the data back to the database.
Problem is, while I think this is a clean solution to handle flipping between two forms, I can't remember ever seeing something like this construction before. Of course, I'm a Genius. (At least, me Ego tells me I am.) But would there be something against using this piece of code or is it just fine?
If the code does what it's supposed to, I think it's fine. I've not seen anything quite like it before either, but it's pretty obvious what it's doing... which in my book is far better than just blindly following other people's work. :-)
I don't see anything wrong with the code. I do question, however, the passing of SharedData to the constructor of both forms. Unless you've overridden the constructor (using reintroduce as well), the argument to TForm.Create() accepts an owner; that owner is responsible for freeing the objects it owns. Since you're freeing them yourself, there's no need to assign an owner; just pass nil to the call to Create() to avoid the overhead of saving the reference, accessing the list of owned objects when one is freed to remove the reference, etc.
Also, Release is designed to be called from within the event handler of the control itself, such as in a button click event. It makes sure that all pending messages are handled before the control is actually freed, in order to avoid AVs. Using it the way you are again adds unnecessary overhead, as you're not using them inside an event handler. You can safely just use Form1.Free; instead.
To clarify use of Release, it's for use in code of the Form itself. For instance, if you have a button on the form, and you want that button's click to cause the form to be freed, you use Release:
procedure TForm1.Button1Click(Sender: TObject);
begin
Self.Release;
Close;
end;
This allows the button click to free the form, but makes sure that the subsequent call to Close runs first. If you use Free instead of Release above, you could end up calling Close on a non-existent Form1. (Chances are it would still be OK, because of the silliness of the code above; the form would still be resident in memory. Still a bad idea, though.)

Form.Release + NIL

if Form.Release is called after using the form, it will free all related memory but not set the form variable to nil.
if not assigned (Form1) then
begin
Application.CreateForm(Tform1, Form1);
try
// Do something
finally
Form1.Release
end;
end;
To be able to call the same code again, Form1 would have to be set to nil at some point. From the description of Release I cannot do
Form1 := nil;
right after Release, because the Release procedure will return directly after being called and before the form is actually freed. I cannot detect when Form.Release is finished to set the form var to nil.
What is the best way to do this?
Put the line
Form1 := nil;
just after the call to Release.
Release is just posting a CM_RELEASE message to the Form which allows the Form to finish what's in its queue (event handlers) before handling the CM_RELEASE message which means normally just calling Free.
So, after calling Release, you should not assume that the Form variable still points to a valid Form, thus putting nil into the variable.
Release is just a (potentially) deferred Free. The 1st thing you should do after calling Release is nilling the variable.
Then, you'll be safe even if some code tries to reference Form1 before it is actually destroyed. In a case like in your code, it would just safely recreate another Form1 for the new usage without bothering the one being destroyed.
Of you could just always call this:
procedure FreeOrReleaseAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
if Temp is TCustomForm then
TCustomForm(Temp).Release
else
Temp.Free;
end;
Be sure to check the type after casting to a TObject as you can't test the type of Obj. This should be safe since like Free, Release is non-virtual.
As is mentioned Release is only a deferred Free for a form to use if it wants to Close/Free itself. Other then being deferred it does nothing different from Release. So there is no use in calling Release in that example. Calling Free seems to be more logical. And you can set it to nil after calling Free or use FreeAndNil.
If you still want to use Release that is fine. Just setting the variable value to nil works. Doing that does not make the forum behave differently. But remember that in this case it is more efficient and more deterministic to call Free instead of Release. My preference is to only use Release where is really needed.
In Delphi Win32, the appropriate way to free objects is to call
FreeAndNil(Form1)
This does both jobs in a single call.
However, I have a sneaking feeling there's more to your question than meets the eye. Are you using Delphi for .NET - and if so, which version?

Resources