Site menuContactSitemapProduktenInfoWie zijn wijWelkomStartDownload



Delphi CompileTime

Marco Wobben 6 aug 2002
[download]
[codecentral] [email]

Introduction
Since a few Delphi versions we are able to put version information in our executables and the list of features has grown to language modules which can be dynamically loaded, multitier data transfer, soap, xml and more and more and more. Yet a simple thing as a compiletime to be available for the application itself has not yet been added to the new features of Delphi.

CompileTime menu
This article describes how an extension to Delphi is made to generate an sources with compiletime information (to be used in the project you're working at). Along the way I will show you several implementations demonstrating how to extend Delphi using different Tools Api Interfaces. The final output will look something like thi
s:

const CompileTime: TDateTime = 37462.6273997917;
const CompileProject = 'C:\...\CompileTime.dpk';
const CompileUnits = 6;
const CompileForms = 0;
const CompileBy = 'Marco Wobben';
const CompilePC = 'ARIES';

const
CompileEMail = 'marcow@bcp-software.nl';
const
CompileURL = 'www.bcp-software.nl'
;
const CompileOS = 'Windows NT v5.0 build 2195';

OpenTools API
A well documented api is the OpenTools api in Delphi. Well documented by Ray Lischner at the time of Delphi 3. Although major changes have been made to these api's the book Ray wrote still stands for the major part and is a good book to read. Based upon insights from this book I finally went on building an extension for Delphi to accomplish a compiler detection system to include some timestamp in code to be compiled in the application. 

TIAddInNotifier
All I've done is assemble a new Delphi designtime package with a single unit in it. This unit contains the single class which does the work. It descents from TIAddInNotifier and overrides some methods:

uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, DesignIntf, ToolIntf;

type
TCompileNotifier = class(TIAddInNotifier)
public
constructor
Create; virtual;
destructor destroy; override;
procedure EventNotification(
NotifyCode: TEventNotification;
var Cancel: boolean); override;
procedure FileNotification(
NotifyCode: TFileNotification;
const FileName: string;
var Cancel: boolean); override;
end;

Before Compile
The interesting method is the EventNotification. This has an NotifyCode as parameter which we can use to detect the instance just before the compilation of our program takes place. This is the place to be to generate an include file to be used in our  project.
Note: This code snippet is simplified and the actual implementation is a tiny bit more complex to service configuration options; but the idea below is the basic functionality.

procedure TCompileNotifier.EventNotification(
NotifyCode: TEventNotification; var Cancel: boolean);
var
P: string;
T: TStringList;
N: TDateTime;
by, pc: string;
L: Cardinal;
WasOpen: boolean;
begin
if
(NotifyCode = enBeforeCompile) and
(ToolServices <> nil) then
begin
// retrieve project directory, generate include file
P := ToolServices.GetProjectName;
// if an unsaved project then abort
if not FileExists(P) or
(ExtractFilePath(P) = '') then Exit;
// create an include file besides the projec file
// so the
project may find it
P := IncludeTrailingPathDelimiter(
ExtractFilePath(P))+
ctFile;
// remember if the include file was already open
WasOpen := ToolServices.IsFileOpen(P);
// generate the contents for the include file
T := TStringList.Create;
try
N := Now;
...
// generate the include files content here
...
// save the new include file
if (not FileExists(P)) or
(not FileIsReadOnly(P)) then
try
T.SaveToFile(P);
except
on
E: Exception do
MessageDlg('Can not write to :'#13#10+
P+#13#10+E.Message,
mtError, [mbOK], 0);
end
finally
T.Free;
// reload the include file if it was already open
if WasOpen then
ToolServices.ReloadFile(P);
end;
end;
end;

Include
Now the include file the expert generates is simple and contains somewhat of the following information. (But off course one may alter depending on the need for information.) Once the package is loaded inro Delphi every project that gets compiled will generate an include file with information about this moment in time. Load your project in Delphi and open the aboutbox form (or something else to display the information). and Do something similar as this: 

implementation

{$R *.dfm}

{$I CompileTime.Inc}

procedure TAboutBox.FormCreate(Sender: TObject);
begin
Label1.Caption :=
'Compiled at '
+DateTimeToStr(CompileTime)+
' by '+CompileBy;
end;

Configuration
Everybody want personalization. And so we add more configuration options. The list of settings is extended offering EMail and URL information to be added to the include file. Also we offer a convenient configuration form in the Tools menu of Delphi. To enable this the code unit is extended with some records to contain personal information which controls the include file content. On the other hand we need the configuration form itself:

CompileTime Settings Dialog
Wizard container

For this we need to create a simple object descendant which will contain the menuitem and the action to be hooked into the IDE and a Form to configure the records with personal information. And a minor detail add persistency to the settings. Important in the design is to leave the core unit as it is, so one does not need the configuration form to compile the expert.

  TCompileTimeWizard = class
private
FConfigAction: TAction;
FConfigMenuItem: TMenuItem;
protected
procedure
OnConfigExecute(Sender: TObject);
procedure RemoveMenuItem;
procedure RemoveToolBar;
public
constructor
Create;
destructor Destroy; override;
end

Wizard create
This wizards life time cycle is managed from the initialization and finalization of the unit containing this class declaration. In turn this instance manages the FConfigAction and the FConfigMenuItem. The code snippet below shows how the wizard prepares the action and adds a menuitem to the Tools menu.

constructor TCompileTimeWizard.Create;
var
I: integer;
Services: INTAServices;
Bmp: TBitmap;
begin
inherited
;
Supports(BorlandIDEServices, INTAServices, Services);
// register the new button on the imagelist
Bmp := TBitmap.Create;
Bmp.LoadFromResourceName(hInstance, 'COMPILETIMEBTN');
I := Services.AddMasked(
Bmp, Bmp.TransparentColor,
'CompileTime.WizardButton');
Bmp.Free;
// create a new action
FConfigAction := TAction.Create(nil);
FConfigAction.Category := 'Tools';
FConfigAction.Caption := 'CompileTime Options...';
FConfigAction.Hint := FConfigAction.Caption;
FConfigAction.ImageIndex := I;
FConfigAction.OnUpdate := nil;
FConfigAction.OnExecute := OnConfigExecute;
// create a menuitem hooked to the action
for I := 0 to Services.MainMenu.Items.Count - 1 do
begin
with
Services.MainMenu.Items[I] do
begin
if (CompareText(Name, 'ToolsMenu') = 0) then
begin
FConfigMenuItem := TMenuItem.Create(nil);
FConfigMenuItem.Action := FConfigAction;
Insert(0, FConfigMenuItem);
Break;
end;
end;
end;
end;

Execute and save
The Actions execute simple starts the form in a modal way and the form takes it from there. Once the ok is pressed the settings are saved back to the records and to the registry using the Services interface to locate the registry entry for the current Delphi version. Note that for the similar operation LoadSettings is not called in the form but by the initialization part of this unit. This way the personal information records are initialized when the package is loaded into the IDE and the core unit is properly setup.

procedure TCompileTimeWizard.OnConfigExecute(
Sender: TObject);
begin
TCompileTimeFrm.Create(nil).ShowModal;
end;

...

procedure TCompileTimeFrm.SaveSettings;
var
Reg: TRegistry;
Services: IOTAServices;
begin
Supports(BorlandIDEServices, IOTAServices, Services);
// convert form controls to settings variable
Settings.FileType :=
TGenerateFileType(FileTypeGrp.ItemIndex);
...
PersonalInfo.URL := UrlEdt.Text;
// save the settings to the current_user registry
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey(
Services.GetBaseRegistryKey+'\CompileTime',
True) then
begin
Reg.WriteInteger('FileType',
Ord(Settings.FileType));
...
Reg.WriteString('URL', PersonalInfo.URL);
end;
finally
Reg.Free;
end;
end;

Wizard destroy
According to the Delphi Help we only need to destroy the MenuItem and the menu no longer has any reference to this wizard. For toolbar buttons however this is different. We manually have to search for any toolbar buttons in all toolbars in the IDE to locate any instance which points to our action and remove them. Since this will lead to disappearing toolbar buttons once the package is unloaded (or if the IDE is started while the package is not yet loaded) we will prevent this from ever happening. We simply do not assign any ActionList to the action, this will make sure the action can not be placed on a toolbar. Note if you wish to experiment uncomment the FConfigAction.ActionList assignment line.

Conclusion
In a simple unit in a single package one can create impressive behaviour improvements which give greater feedback about the executable a user is working with. No more file timestamps to check, simply add the include file to your about box, display some constant values and all the information your helpdesk requires is available.

If you want this available in your Delphi version, open the Package from the zipfile and press the install button from the Package Manager or simply add the bpl file itself. Now you're ready, open a project and go include.


Copyright © 2002 Bommeljé Crompvoets en partners

Het is toegestaan dit artikel in zijn geheel te kopiëren en te verspreiden, mits de tekst woordelijk in tact blijft en deze notitie bevat. Het is toegestaan om deze tekst te citeren, of te wijzigen, mits de oorspronkelijke auteur en houder van het copyright vermeld worden.
You may republish this paper verbatim, including this notation. You may update, correct, or expand the material, provided that you include a notation stating the original author and copyright holder.








[English] [welkom] [wie zijn wij] [info] [diensten] [producten] [download] [contact] [verwijzingen]