Jump to content
Rollo62

Best way to set early, global variables in a project, before build-time

Recommended Posts

Posted (edited)

Hi there,

I was looking into @Uwe Raabe's CmonLib at Github and found here a global TUtilities record.

 

type
  TUtilities = record
  private
  class var
    FAppName: string;
    FCompanyName: string;
  public
    ...
    class property AppName: string read FAppName write FAppName;
    class property CompanyName: string read FCompanyName write FCompanyName;
  end;

I'm doing something similar, to provide the global CompanyName and AppName for various purposes, but best of all as early as possible in the process,
for example for logging, or the like.

Where doese CompanyName, AppName come from,. do you do this in code?

What would be the best approach to provide such data for each project in a simple way, as early as possible from external data?
 

- I would love to load such data from settings-file *.json, *ini, *yaml or whatever before compilation, unfortunately I think there is no option to do so.

- Using ressources, deployment or the like to include such settings-file could work, but when analysing the content, its already much too late in the startup process.
   This might work only for certain cases, but not the most earliest.

- Ok, I could use the IDE's global defines option manually, to add each of the desired data, but I really don't like to fumble in that nasty options dialog by hand.

- Yes, I could use pre-build-events to manage that from a settings-file, but thats also only a half-cool solution.

- And I could write my IDE expert doing this, perhaps there are already some available, but I like to have my IDE as clean from experts as possible.

- I could use my own StartUpCopy.pas in the *.dpr, to define the earliest settings there, also a too clumsy solution.
- Actually I simply added a local settings-include-file (*.inc) , with defining data as defines, and include this wherever needed, which is also not the best solution.

Do you have similar needs and how do you do that, is there any cool solution I have not considered yet?


 

 

Edited by Rollo62

Share this post


Link to post
1 hour ago, Rollo62 said:

but best of all as early as possible in the process,

I'd like to ask for more details about the term early in this context. Is this targeting the initialization sections of some units or more the code in the dpr begin end section?

 

I admit that I often fell into the trap to put too much code into the initialization section, but I found that it heavily reduces the possibility to tweak the behavior based on a configuration or on a per project basis in case these units were used in several projects. That was a driving force for the invention of Cmon.Initializing.

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.

While it is still a long way to get all (probably better most of) my projects suffering from the above drawback, this approach is almost mandatory for new projects now.

 

As a couple of units of CmonLib make use of this, one can find some hints for usage in the examples (still successfully procrastinating the CmonLib documentation, I know).

 

Let me show a simple example from Cmon.Messaging.Dialogs.Vcl. This is how it would be written the old way:

var
  Instance: TDlgMessageHandlerVcl = nil;

initialization
  Instance := TDlgMessageHandlerVcl.Create;
finalization
  Instance.Free;
end.

As a result, simply using this unit will create the instance, which registers itself catching the corresponding messages.

 

And this how it looks making use of Cmon.Initializing:

var
  Instance: TDlgMessageHandlerVcl = nil;

{ will be called in Application.Initialize after all other initialization code has been executed }
procedure InitHandler;
begin
  if TDlgMessage.AutoRegisterHandler and TDlgMessageHandlerVcl.AutoRegisterHandler then
    Instance := TDlgMessageHandlerVcl.Create;
end;

initialization
  TDlgMessageHandlerVcl.AutoRegisterHandler := True;
  TInitialize.AddInitProc(InitHandler);
finalization
  Instance.Free;
end.

Now creating the instance will take place during Application.Initialize, which allows us to write some code tweak the behavior. As of my personal preference I put all this pre-initialize code in s similar named procedure, called immediately before:

program DialogsDemoVCL;

uses
  Vcl.Forms,
  Cmon.Messaging.Dialogs.Vcl,
  Main.VclForm in 'Main.VclForm.pas' {MainForm},
  Utilities in 'Utilities.pas';

{$R *.res}

procedure PreInitialize;
begin
  { Per default using Cmon.Messaging.Dialogs.Vcl will automatically register the containing message handler during
    Application.Initialize. To optionally disable auto registering of all DlgMessage handlers this is one place to do.
    You need to add Cmon.Messaging to the uses to make it compile. }
//  TDlgMessage.AutoRegisterHandler := False;

  { You can as well disable automatic registration of indivudual handlers. That implies to leave the corresponding setting
    above at True of course. }
//  TDlgMessageHandlerVcl.AutoRegisterHandler := False;
end;

begin
  PreInitialize;
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

You can find a similar approach in Cmon.Messaging.Logging.TextFile

Share this post


Link to post
3 hours ago, Rollo62 said:

as early as possible in the process,

Something like this 

set filename=MyAppConsts.pas
@echo on
echo building %filename%
@echo off


(echo unit Unit1;& echo.& echo interface& echo.& echo const) > %filename% 

echo   APPLICATION_APP_NAME = 'MyApp';>> %filename% 
echo   APPLICATION_COMPANY_NAME = 'MyCompany';>> %filename% 


(echo.& echo implementation& echo.& echo end.) >> %filename% 

@echo on
@echo done building %filename%


#exit 0

the result a file "MyAppConsts.pas" 

unit Unit1;

interface

const
  APPLICATION_APP_NAME = 'MyApp'; 
  APPLICATION_COMPANY_NAME = 'MyCompany'; 

implementation

end.

there is no earlier than that !

Share this post


Link to post
Posted (edited)

@Uwe Raabe

Very interesting, I will look into that.
Its perhaps not needed to be before unit initializations, but yes, if that is possible, then its OK too.
I have two main topics currently for that, "Logging" and "Setup file access".
By the latter I mean, to load variable settings from files, not at compile time, but at runtime.

For this I could see sometimes the need to access that as soon as possible, while the "FileNamesAndPaths" might not be setup yet.
Of course I can hardcode the fractions or subfolders in the "FileNamesAndPaths", but exactly this I want to avoid.

 

I usually avoid initialization sections and try to make such lazy app initializations only in the FormShow event, to stay as mobile friendly, as possible.
But that doesn't always work.
At least a handful of fixed constants would be good to be store outside the IDE project, to easy handle it.

Regarding your proposal, my thought was to encapsule this in its own StartUp unit:

uses
  My.StartUp,
  Vcl.Forms,
  Main.VclForm in 'Main.VclForm.pas' {MainForm};

{$R *.res}

begin
  TMyStartUp.PreInitialize;

  Application.Initialize;
  ...

But again, if needed earlier it might fail.

@Kas Ob.
I'm not getting your point 100%, but assume you mean an Pre-Build-Event calling an batch file.
Yes, that would be possible, but again, I have to setup all this in the IDE before.
Or do you mean there is the possibility in the Tools/Options or else, to set such event globally, one for all projects?

Yes, maybe creating my own initial project in the repository is an option too, but this I would need to maintain too, with each IDE update.

 

Edited by Rollo62

Share this post


Link to post
46 minutes ago, Rollo62 said:

Or do you mean there is the possibility in the Tools/Options or else, to set such event globally, one for all projects?

Well that is possible, but it might be an unneeded extra work.

 

What i meant is something like this

image.thumb.png.4f2c0f70cae5b1f713c8f12204e2e93b.png

Just include the batch file which can have relative path, also it can take a parameter for the output path, even the name of the output file.

and from the dproj file

    <PropertyGroup Condition="'$(Base)'!=''">
        <PostBuildEvent><![CDATA[BuildConst.bat
$(PostBuildEvent)]]></PostBuildEvent>

Also i did always prefer using the inc files in place for naming and versioning, over batch files, thus i have their content in the project and in the EXE description ... 

  • Like 1

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

×