Jump to content
GabrielMoraru

Stop abusing global variables (improving TApplication architecture)

Recommended Posts

Well.... we know that global variables should not be used and I don't use them in my programs. 
What I don't like is that Delphi automatically creates a global variable for each new form. This is especially bad for beginners, which might understand that is good to have global variables all over the place. 

 

Sooo... I wanted to get rid of them... and I did (some years ago).

I created a framework called Delphi LightSaber. It does all kind of things like saving/restoring the form position on the screen when you restart the app, global logging, installer, self restart, self delete, app single instance, GUI language translation... stuff like that.

There I replace VCL's TApplication object with my own. Involves a bit of heart surgery (more than I like) but unfortunately necessary in order to implement some low level stuff. Works perfectly. 

Now my apps are global var free!


The DPR looks like this:

begin
  AppData:= TAppData.Create('Light Template Micro');
  AppData.CreateMainForm(TfrmMain, TRUE, TRUE, asFull);
  AppData.Run;
end.

 

Recently I ported the code to FMX also . But there are dragons: among them: the forms are not created immediately. They are created later by RealCreateForms. 

Under VCL I was providing a "fake" (local) variable to Application.CreateForm. 
But this is not working anymore under FMX (because of the delayed creation):

procedure TAppData.CreateMainForm(aClass: TComponentClass; AutoState: TAutoState = asPosOnly);
var aReference: TForm;  //NOPE! FMX stores the ADDRESS of the variable you passed! Once the procedure is over, the variable is gone but FMX still holds this stack address (now occupied by something else)
begin
  CreateMainForm(aClass, aReference, AutoState);   // Reference is NIL here because of the form is created later (by RealCreateForms)
end;

 


Any idea on how to approach this without hacking even deeper into the TApplication class?

A brutal approach would be to call RealCreateForms early. Then the forms are created normal, like in VCL.
Another idea would be to store a list of pointers (one for each form). Relatively low code, but I need to stay in sync in case the user DO want to use some global variables for some forms (or all).

 

 

 

Edited by GabrielMoraru

Share this post


Link to post

The RTL/VCL/FMX libraries use a lot of globals internals. Striving to avoid globals in your own code is a good goal, but don't try to avoid it in library code that you don't own and is not under your control.

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post

By using

12 hours ago, GabrielMoraru said:

Any idea on how to approach this without hacking even deeper into the TApplication class?

By using anonymous procedures, instead of calling with parameters directly.
The question is how you could trigger that call to the anon proc in the right sequence.
After the real CreateMainForm a message is sended.

TMessageManager.DefaultManager.SendMessage(Self, TMainFormChangedMessage.Create(FMainForm));

There is also a TMainFormChangedMessage or FormAfterCreateHandler which could make sense here.

procedure TApplicationEvents.FormAfterCreateHandler(const ASender: TObject; const AMessage: TMessage);

//or better


procedure TApplication.SetMainForm(const Value: TCommonCustomForm);
begin
  if FMainForm <> Value then
  begin
    FMainForm := Value;
    TMessageManager.DefaultManager.SendMessage(Self, TMainFormChangedMessage.Create(FMainForm));
  end;
end;

 

Perhaps you can catch one of these in your class and try to re-fine the desired settings you want to change.

 

  • Like 1

Share this post


Link to post
13 hours ago, GabrielMoraru said:

There I replace VCL's TApplication object with my own

What are you actually trying to achieve? What issues with VCL are you addressing? 

 

13 hours ago, GabrielMoraru said:

But there are still some that Delphi make use swallow: the global var for each form we create.

Certainly the VCL does not enforce any such thing. Perhaps you misspoke when you wrote this. 

Share this post


Link to post

I understand your attempt to make the initial application creation more usable and flexible.

AppData:= TAppData.Create('Light Template Micro');
AppData.CreateMainForm(TfrmMain, TRUE, TRUE, asFull);

But don't forget that the *.dpr is managed a lot by the IDE and fumbling into this can create various issues.
If you want to manage that on your own, I would suggest doing this in another, separate unit, not the *.dpr itself.

 

Edited by Rollo62
  • Like 3

Share this post


Link to post
20 hours ago, GabrielMoraru said:

Well.... we know that global variables should not be used and I don't use them in my programs. 
But there are still some that Delphi make use swallow: the global var for each form we create.

The first thing I do after creating a new form is remove it from auto form creation and delete the global variable that the IDE adds for me. You don't have to swallow anything.

 

Avoiding global variables is good, but as a practical matter it is not possible to avoid them 100%. You can pretend to avoid them by covering them up with class variables or other methods of locking the global variable behind additional layers, but in the end you are still going to have globals for your application, main form, etc., even if you limit their accessibility. I don't see the point in trying to remove them from code I don't personally maintain.

  • Like 4

Share this post


Link to post
11 hours ago, David Heffernan said:

Certainly the VCL does not enforce any such thing. Perhaps you misspoke when you wrote this.  

Hi David. I rephrased that to: "What I don't like is that Delphi automatically creates a global variable for each new form. This is especially bad for beginners, which might understand that is good to have global variables all over the place".

 

5 hours ago, Brandon Staggs said:

The first thing I do after creating a new form is remove it from auto form creation and delete the global variable that the IDE adds for me. You don't have to swallow anything.

I do the same 🙂 

 

5 hours ago, Brandon Staggs said:

I don't see the point in trying to remove them from code I don't personally maintain.

I only try to:

1. declare my own global vars and 

2. use forms without using their associated global variables.

Nothing more 🙂

 

14 hours ago, Remy Lebeau said:

The RTL/VCL/FMX libraries use a lot of globals internals. Striving to avoid globals in your own code is a good goal, but don't try to avoid it in library code that you don't own and is not under your control.

I know.
Same answer as above 🙂

 

5 hours ago, Brandon Staggs said:

but in the end you are still going to have globals for your application, main form

Actually, any app can run nicely without using main form's global var. That var can also be deleted.

 

Otherwise I have a single global var in my apps instantiating a class called TCore (this is a specialized class for each app). TCore is responsible for creating whatever internal objects, loading the data from disk (resuming the state) and sending the computed data to GUI. My forms are almost voided of any code. Mostly they only have simple event handlers that answers to clicks, send the event further to a specialized class to process it and display the result on screen.

Total separation between GUI and business logic.

This allows porting the app to FMX easily or converting the GUI app to a console app.

 

Whatever global vars (I know there are plenty) Delphi has under the hood... well... we can't do much about that. I hope Delphi mange them well

Edited by GabrielMoraru

Share this post


Link to post
10 hours ago, Rollo62 said:

I understand your attempt to make the initial application creation more usable and flexible.


AppData:= TAppData.Create('Light Template Micro');
AppData.CreateMainForm(TfrmMain, TRUE, TRUE, asFull);

But don't forget that the *.dpr is managed a lot by the IDE and fumbling into this can create various issues.

I know. But the code above does not seem to affect IDE's behavior in any way. I used that for many many years (I and other).

-
Some things needs to be setup really early before the application has a chance to fully run. One example is the "single instance" feature: when you double click a file that is associated with your app (this is also self-managed by TAppData), the OS tries to execute the app, to open that file. The app starts but not fully (no main form), checks if another instance already exists, if yes, it sends the file to that existing instance and the shut down itself. 

The logging system has to start also really early. 

_
It would be cool if Embarcadero would include such features in TApplication so we don't have to do heart surgery 🙂
 

Edited by GabrielMoraru

Share this post


Link to post
58 minutes ago, GabrielMoraru said:

Some things needs to be setup really early before the application has a chance to fully run. One example is the "single instance" feature: when you double click a file that is associated with your app (this is also self-managed by TAppData), the OS tries to execute the app, to open that file. The app starts but not fully (no main form), checks if another instance already exists, if yes, it sends the file to that existing instance and the shut down itself. 

The logging system has to start also really early. 

_
It would be cool if Embarcadero would include such features in TApplication so we don't have to do heart surgery

I may not understand what you are driving at, but deciding whether to proceed with TApplication.Initialize/createform/run or just passing the command line off to an open instance after checking a mutex is easily done in the dpr. Just do your check and skip the whole application there if necessary. It's never felt particularly difficult or confusing to me to add code to the dpr, but maybe you have different needs.

Share this post


Link to post
1 hour ago, GabrielMoraru said:

Delphi automatically creates a global variable for each new form. This is especially bad for beginners, which might understand that is good to have global variables all over the place".

I think it's perfectly fine for "beginners". They can learn better patterns as (if) they become more experienced - like we've all done.

  • Like 1

Share this post


Link to post
10 hours ago, GabrielMoraru said:

Some things needs to be setup really early before the application has a chance to fully run. 

Exactly, thats why I place my unit here in the *.dpr at high position, similar to System.StartUpCopy.
I can use my special functions or classes then still later on in the *.dpr, if you prefer that.

 

uses
  System.StartUpCopy,
  System.Math,
  FMX.Types,
  FMX.Forms,
  My.App.StartUp,
  ...

 

Share this post


Link to post
2 hours ago, Rollo62 said:

Exactly, thats why I place my unit here in the *.dpr at high position, similar to System.StartUpCopy.

You are aware that System.StartUpCopy exposes only an exception declaration and that the actual code is executed in the units initialization section?

So it isn't placed in that position to provide any functions or classes to the code inside the DPR (which would work on any position btw.), but rather to have some required files at the expected places for other units executing code in their initialization section.

 

Taking the "single instance" feature from above, executing code in initialization sections could be problematic or just in vain best. After all the "single instance" may just shut down after notifying the other instance. Executing any other code  during startup may interfere with the running instance.

 

My CmonLib provides a way to tackle this:

Quote

Cmon.Initializing establishes a way to control initialize code for other units into the call to Application.Initialize. This allows to make adjustments before, which would be near to impossible if the initialize code would execute in the units initialization section.

 

  • Like 4

Share this post


Link to post

Long live the Initialization Sections in their glorious randomness 🙂 🙂 🙂.

 

_____


But honestly, putting something in an initialization section and hoping that your code will execute somehow, somewhere, "soon enough" is madness... at best.

I think (I hope I am not dreaming) Marco Cantu mentioned once that they are looking into making the Initialization sections non-random. 

 

But even after that, I don't think I could learn to love them. Even if they are not random, it just executes code "async" with the normal execution of your program.

A programmer that looks into your (large) project will see some code being executed "magically" at the program startup. I know that, I have been there. 

 

Just write a class or function and make sure you call it explicitly, at the appropriate step during program startup, before GUI (probably). 

Edited by GabrielMoraru

Share this post


Link to post
On 11/26/2025 at 7:38 AM, Rollo62 said:

Exactly, thats why I place my unit here in the *.dpr at high position, similar to System.StartUpCopy.

And what if somebody (a new programmer that doe not know that the units must have a magic order) changes the order?

And what if you need to add a new unit on top of that. FastMM is a very very good example!

And what if a 3rd party tool is inserting their own units on top of yours? I hated when eureka was doing that! And it is not the only one to do that!

 

Relying on "magic" order is never a good idea. 

 

Share this post


Link to post
On 11/26/2025 at 10:44 AM, Uwe Raabe said:

My CmonLib provides a way to tackle this

Looks like my LightSaber and your CmonLib library have lots of stuff (features) in common.🙂 
But our approaches are quite different.

Edited by GabrielMoraru

Share this post


Link to post
2 hours ago, GabrielMoraru said:

But our approaches are quite different.

Which is a good thing, IMHO.

  • Like 2

Share this post


Link to post
12 hours ago, GabrielMoraru said:

Just write a class or function and make sure you call it explicitly, at the appropriate step during program startup, before GUI (probably). 

Well it is not that simple everytime with every application, you attached GUI at the end and that is perfectly makes sense most the time, ..

 

As example; if you are going to have theming of sort then you are bound to initialize the code in initialization clause at some point, liked it or not, it is inevitable, this is the Delphi/Pascal way, and that simple because TApplication is hard coded and auto initialized, some hooking hooking must be put there, this unlike many other language and platforms (like Java).

 

For me as role of thumb, never put or use business logic in initialization, but keep that initialization to the infrastructure like logging or resource loading/sharing, caching and reusing anything from memory allocation to DB connection to files .. etc

 

and let me bring one example about how you even can't depend on TApplication and/or any derived (hooked/abused/enhanced/patched) instance of it, if you tried to write multiple Windows services in one EXE then you will know that your TApplication there has completely different meaning and usage, yes multiple Windows Services can (for me it is recommended) to be in one single EXE, the OS will load and initialize the EXE once and then start each needed-to-be-run service within the same loaded EXE, so initialization of singletons and globals are the backbone for these services.

 

As for randomness, it is not really random with class functions you can ask and build them to chain without randomness, the only shortcoming is that the compiler is lacking a specific warnings and errors to prevent you form shooting your foot, but again this might happen if you went all in initialization clause without thinking it through, some stuff like logging, is almost must to be initialized in its own unit before running any code.

 

And in short ....

10 hours ago, Uwe Raabe said:
12 hours ago, GabrielMoraru said:

But our approaches are quite different.

Which is a good thing, IMHO.

+1

 

  • Like 1

Share this post


Link to post
11 hours ago, Kas Ob. said:

and that simple because TApplication is hard coded and auto initialized,

Yes. It is sad that we can't derive our own calls from TApplication and run that. 
Most people won't need it, but when you need it, you need to do some dirty tricks in order to make it work.
__

Maybe we should start a feature request?

Edited by GabrielMoraru

Share this post


Link to post
13 hours ago, GabrielMoraru said:

Maybe we should start a feature request?

I scratched my head and thought then thought, and couldn't find something solid to write except few thoughts

 

1) If memory didn't fail me i already few suggestions for TCustomApplication something more or less like this

type
  TMyApplication = class(TCustomApplication)
    procedure CreateForm(InstanceClass: TComponentClass; var Reference); override;
    procedure Idle(Wait: Boolean); override;
  end;

procedure TMyApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
begin
  inherited;
  MyLoggingService.LogFormCreation(TForm(Reference));
end;

begin
  RegisterApplicationInstance(TMyApplication.Create(nil));
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end;

And that would be clean or to be exact the cleanest way to do it, yet TApplication is hardcoded with no virtual methods and only one protected, 

Having something like RegisterApplicationInstance is perfect solution, but what about the performance hit?,

well it would be minimal and even negligible as there is so many other places speed can be squeezed even in TApplication itself !

 

2) The ability to extend TApplication is great and very useful, from logging done right, to theming done right.. 

3) Screen:TScreen has the same carved in the stone approach, with the moto we did the best and no one can do it better.

4) Extending TApplication would make impact on so many 3dParty libraries and simply will remove the dangerous hooking (hot patching) allowing DEP as it intended, it will provide clean method for extending the VCL.

 

I didn't log to the old portal and don't even know if is still working, so it might be there already, yet new opening request might help collecting votes if that is help.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×