Apparently, when your application is a FireMonkey application.
If you have any applications which contain code similar to this:
Application.CreateForm(TMyForm, MyForm); MyForm.InitialProperty := SomeInitialValue; Application.Run;
Then you will need to rethink how you initialise those forms in the FireMonkey framework because CreateForm() commits what I consider to be a cardinal sin (in the software development world at least):
It doesn’t do what it says it does!
In a VCL application, when you call CreateForm( class, ref ) an instance of class is created and a reference to it placed in ref, which you can then use to initialise or otherwise reference the instantiated form.
Try and do this in a FireMonkey application and it blows up in your face.
In a FireMonkey application when you call CreateForm no form is created at all!
Instead, the Application object simply takes a note of the class and the address of the reference variable provided in an internal array. No instance is created and the reference is not assigned until you call Application.Run (or alternatively the new Application.RealCreateForms).
This all has a really, really bad smell about it and frankly beggars belief.
Why go to the trouble of preserving the appearance of the behaviour of one framework in another if you are not going to preserve the behaviour itself ?
Or put more simply – if FireMonkey works differently then for crying out loud make it look different in those areas where those differences are important!
In this case, CreateForm() should have been replaced by something which was clearer in conveying the intended and expected behaviour (or rather, non-behaviour).
I suggest something along the lines of:
procedure TApplication.AddForm(const aFormClass: TComponentClass; const aReferenceVar: PObject);
Which would then have been called like this:
Application.AddForm(TMyForm, @MyForm);
You would not then have needed the very smelly “RealCreateForms” method (any method which suggests that it is really doing something in place of some other thing which gave the impression of having done something but really didn’t is strongly suggestive to me of code that needs some serious re-thinking).
Or rather, you would have had a method you could call more simply and honestly… CreateForms.
i.e. two methods which both do what they say, rather than the current situation of one method which doesn’t do what it says and another method whose name contains an apology for the poor behaviour of it’s related sibling.
Two things would make this better than what we actually have imho:
- It is different from what we had (and still have) in the VCL, which is good and proper because it is – after all – doing something quite different too!
- The parameter list is more clearly expressing the behaviour of the method. It does not modify the reference variable provided and asked for, it is asking for a pointer to a reference variable.
It is far more readily apparent that whatever is going to happen to that reference is not going to occur now but at some later point.
Another fool who uses CreateForm when you should NEVER use CreateForm. EVER.
Create your form object the same way you create EVERY other object in Object Pascal.
:= .Create(parameters);
ie:
Form1 := TForm1.Create(Nil);
Anything else is asking the system to invoke magic, why then be surprised when the magic is not to your liking?
Don’t be so quick to judge the foolishness of someone.
I came across this because my testing framework replaces the usual Application.Initialise/CreateForm/Run sequence with it’s own initialisation. Doing that I *HAVE* to call CreateForm because there is no other way to establish the MainForm of the TApplication object (* see below) . I *needed* that magic. The whole point is that in FireMonkey that magic now occurs not in CreateForm but in REALCreateForm, so as well as my form not being created and the “var” reference that I passed in coming back out exactly as it went in (NIL), the “magic” wasn’t happening either.
* in another bizarre example of difference, the VCL TApplication.MainForm property remains read-only but in FMX is a read/write property which again begs the question, why go to all these lengths to preserve superficial similarity with the VCL when things clearly work so completely differently? Why persist in reproducing the “first created form is the main form” behaviour using a method which mimics the VCL methods but does not behave like the VCL ?
This all has a *very* nasty smell about it. It simply doesn’t look very well thought out at all.
And what I really can’t fathom is that if I simply call RealCreateForms() after CreateForm() but before calling “Run” then my form gets instantiated, my reference get’s updated and everything is once again tickety boo. Which beg’s the question, why does CreateForm() not simply do the instantiation (and the magic) like it always used to?
I otherwise absolutely agree with you that you should only use direct instantiation, but I am also aware that there are some people who *disagree* with that p.o.v, and up until now there really was no real reason – other than personal preference – for them to change their ways.
@Jolyon: I agree with what you said above. When I first came to Delphi some five to six years ago, I needed quite some time to figure out what was going on in this CreateForm() call. I try to avoid “var” parameters where possible as there is always a chance of strange stuff happening behind the scences. The funny thing is, it does not even work as advertised in the VCL: The first parameter is a happy TComponentClass and so CreateForm() works for any TComponent that should be owned by TApplication, after all that is how TDataModules come to life.
In some of the last projects I have worked with an “application controller” object that directly descends from TComponent and controls application lifetime. The main form is also established by this object. Still, I have to use Application.CreateForm() for that purpose as it is the only way to have a MainForm within the VCL. I had rather seen a clear cut with the new FireMonkey framework.
Yes, and of course (in a VCL application) there has to be at least ONE call to Application.CreateForm(), and it is not all that uncommon or unusual for someone to inject some initialisation of that instantiated form to occur before the Application.Run call. That doesn’t mean that someone doing that is then using Application.CreateForm() everywhere else.
Using TApplication.CreateForm is not foolish thing. It is a creational pattern.
Regards
What if there is in fact reasons for doing what it’s doing?
I can easily imagine several. What if runtime order dependencies from one form to another require that we register all the forms, and do some quick checks, perhaps determine the ideal startup/creation order of the forms in a way that is truly failsafe, and no matter the order that application.CreateForm entries are added, everything still works?
W
You have missed the point entirely. I have no issue with the fact that it may have to work differently, but if that is the case, why go to lengths to make it LOOK like it works exactly as it doesn’t ?
Apart from anything else, CreateForm breaks the contract established in it’s parameter declarations.
I agree with your wondering, if this is what is happening. I am also trying to replace the default form creation in my applications. But I typically still use Application.CreateForm, for example as follows:
Application.CreateForm(TMyDatabase, MyDatabase); MyDatabase.RecreateDb := FindCmdLineSwitch('recreatedb'); MyDatabase.InitTables := FindCmdLineSwitch('init'); Application.CreateForm(TMyMainForm, MainForm); MainForm.Database := MyDatabase;
It seems this style won’t do with OSX.
OTOH, I am happy, if it can be done more straight forward, but I wonder would it work, if CreateForm cannot work like that.
Well, apparently this won’t do withe OSX…