Can I determine the order in which my units have been initialized?
Asked Answered
N

5

16

I am hunting a bug which might be connected to unit initialization order. Is there a way to see which initialization section was executed when? I need to know the order. This is during debugging, so I have the full power of the Delphi IDE, in my case Delphi 2009.

I could set breakpoints, but this is rather tedious when having many units.

Do you have any suggestions?

Neelon answered 2/11, 2010 at 9:39 Comment(1)
Related: If you use a unit in the interface section you know that that unit will be initialized BEFORE the unit that uses that unit. When using units in the implementation section this is not the case. So usually when you are using a unit with a singleton in it, created in it's initialization section, you should use that unit in the interface section to make sure that it is initialized before use.Ruche
R
8

For units in the interface uses list, the initialization sections of the units used by a client are executed in the order in which the units appear in the client's uses clause.

see Online Help \ Programs and Units \ The Initialization Section and this article: Understanding Delphi Unit initialization order

ICARUS computes the Runtime initialization order for its Uses Report:

This section lists the order in which the initialization sections are executed at runtime.

Rempe answered 2/11, 2010 at 10:17 Comment(3)
The part that makes it difficult is when Unit1 uses several other Units which may use even more Units.Jamilla
@Heinrich: Try ICARUS for the calculation of the runtime initialization order.Rempe
@Rempe Do you have any idea where ICARUS and Understanding Delphi Unit initialization order have moved to?Photojournalism
J
13

Here is some code I just tested in D2010, note that you need to set a Breakpoint in System.InitUnits and get the address of InitContext var (@InitContext). Then modify CtxPtr to have this address WHILE STILL RUNNING. (Maybe someone knows a smarter way for this).

procedure TForm3.Button2Click(Sender: TObject);
var
  sl: TStringList;
  ps: PShortString;
  CtxPtr: PInitContext;
begin
  // Get the address by setting a BP in SysUtils.InitUnits (or map file?)
  CtxPtr := PInitContext($4C3AE8);

  sl := TStringList.Create;
  try
    ps := CtxPtr^.Module^.TypeInfo^.UnitNames;

    for i := 0 to CtxPtr^.Module^.TypeInfo^.UnitCount - 1 do
    begin
      sl.Add(ps^);
      // Move to next unit
      DWORD(ps) := DWORD(ps) + Length(ps^) + 1;
    end;

    Memo1.Lines.Assign(sl);
  finally
    sl.Free;
  end;
end;

/EDIT: and here is a version using JclDebug and a mapfile:

type
  TForm3 = class(TForm)
  ...
  private
    { Private declarations }
    var
      Segments: array of DWORD;
    procedure PublicsByValue(Sender: TObject; const Address: TJclMapAddress; const Name: string);
    procedure MapSegment(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const GroupName, UnitName: string);
    procedure MapClassTable(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const SectionName, GroupName: string);
  public
    { Public declarations }
  end;

var
  Form3: TForm3;
  CtxPtr: PInitContext = nil; // Global var

procedure TForm3.MapClassTable(Sender: TObject; const Address: TJclMapAddress;
  Len: Integer; const SectionName, GroupName: string);
begin
  SetLength(Segments, Length(Segments) + 1);
  SegMents[Address.Segment-1] := Address.Offset;
end;

procedure TForm3.PublicsByValue(Sender: TObject; const Address: TJclMapAddress;
  const Name: string);
const
  InitContextStr = 'System.InitContext';
begin
  if RightStr(Name, Length(InitContextStr)) = InitContextStr then
  begin
    CtxPtr := PInitContext(Segments[Address.Segment-1] + Address.Offset);
  end;
end;

procedure TForm3.Button2Click(Sender: TObject);
var
  MapParser: TJclMapParser;
  MapFile: String;
  sl: TStringList;
  ps: PShortString;
  i: Integer;
begin
  MapFile := ChangeFileExt(Application.ExeName, '.map');

  MapParser := TJclMapParser.Create(MapFile);
  try
    MapParser.OnPublicsByValue := PublicsByValue;
    MapParser.OnClassTable := MapClassTable;
    MapParser.Parse;
  finally
    MapParser.Free;
  end;

  if CtxPtr = nil then
    Exit;

  sl := TStringList.Create;
  try
    ps := CtxPtr^.Module^.TypeInfo^.UnitNames;

    for i := 0 to CtxPtr^.Module^.TypeInfo^.UnitCount - 1 do
    begin
      sl.Add(ps^);
      // Move to next unit
      DWORD(ps) := DWORD(ps) + Length(ps^) + 1;
    end;

    Memo1.Lines.Assign(sl);
  finally
    sl.Free;
  end;
end;

Output in my case:

Variants
VarUtils
Windows
Types
SysInit
System
SysConst
SysUtils
Character
RTLConsts
Math
StrUtils
ImageHlp
MainUnit
JwaWinNetWk
JwaWinType
JwaWinNT
JwaWinDLLNames
JwaWinError
StdCtrls
Dwmapi
UxTheme
SyncObjs
Classes
ActiveX
Messages
TypInfo
TimeSpan
CommCtrl
Themes
Controls
Forms
StdActns
ComCtrls
CommDlg
ShlObj
StructuredQueryCondition
PropSys
ObjectArray
UrlMon
WinInet
RegStr
ShellAPI
ComStrs
Consts
Printers
Graphics
Registry
IniFiles
IOUtils
Masks
DateUtils
Wincodec
WinSpool
ActnList
Menus
ImgList
Contnrs
GraphUtil
ZLib
ListActns
ExtCtrls
Dialogs
HelpIntfs
MultiMon
Dlgs
WideStrUtils
ToolWin
RichEdit
Clipbrd
FlatSB
Imm
TpcShrd

/EDIT2: And here a version for D2009 (requires JclDebug):

unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StrUtils, JclDebug, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    var
      Segments: array of DWORD;
    procedure PublicsByValue(Sender: TObject; const Address: TJclMapAddress; const Name: string);
    procedure MapClassTable(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const SectionName, GroupName: string);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  CtxPtr: PInitContext = nil; // Global var
  Symbols: TStringList;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  MapParser: TJclMapParser;
  MapFile: String;
  sl: TStringList;
  ps: PShortString;
  i: Integer;
  s: String;
  Idx: Integer;
begin
  MapFile := ChangeFileExt(Application.ExeName, '.map');

  MapParser := TJclMapParser.Create(MapFile);
  try
    MapParser.OnPublicsByValue := PublicsByValue;
    MapParser.OnClassTable := MapClassTable;
    Memo1.Lines.BeginUpdate;
    MapParser.Parse;
    Memo1.Lines.EndUpdate;

  finally
    MapParser.Free;
  end;

  if CtxPtr = nil then
    Exit;

  sl := TStringList.Create;
  try

    for i := 0 to CtxPtr^.InitTable.UnitCount-1 do
    begin
      if Assigned(CtxPtr^.InitTable.UnitInfo^[i].Init) then
      begin
        s := Format('$%.8x', [DWORD(CtxPtr^.InitTable.UnitInfo^[i].Init)]);
        Idx := Symbols.IndexOfObject(TObject(CtxPtr^.InitTable.UnitInfo^[i].Init));
        if Idx > -1 then
        begin
          Memo1.Lines.Add(Format('%.4d: %s', [i, Symbols[Idx]]));
        end;
      end;
    end;

  finally
    sl.Free;
  end;
end;

procedure TForm1.MapClassTable(Sender: TObject; const Address: TJclMapAddress;
  Len: Integer; const SectionName, GroupName: string);
begin
  SetLength(Segments, Length(Segments) + 1);
  SegMents[Address.Segment-1] := Address.Offset;
end;

procedure TForm1.PublicsByValue(Sender: TObject; const Address: TJclMapAddress;
  const Name: string);
const
  InitContextStr = 'System.InitContext';
begin
  if RightStr(Name, Length(InitContextStr)) = InitContextStr then
  begin
    CtxPtr := PInitContext(Segments[Address.Segment-1] + Address.Offset);
  end
  else begin
    Symbols.AddObject(Name, TObject(Segments[Address.Segment-1] + Address.Offset));
  end;
end;

initialization
  Symbols := TStringList.Create;
  Symbols.Sorted := True;
  Symbols.Duplicates := dupIgnore;

finalization
  FreeAndNil(Symbols);

end.

Output on my system (Unitname.Unitname is actually Unitname.Initialization):

0001: System.System
0003: Windows.Windows
0011: SysUtils.SysUtils
0012: VarUtils.VarUtils
0013: Variants.Variants
0014: TypInfo.TypInfo
0016: Classes.Classes
0017: IniFiles.IniFiles
0018: Registry.Registry
0020: Graphics.Graphics
0023: SyncObjs.SyncObjs
0024: UxTheme.UxTheme
0025: MultiMon.MultiMon
0027: ActnList.ActnList
0028: DwmApi.DwmApi
0029: Controls.Controls
0030: Themes.Themes
0032: Menus.Menus
0033: HelpIntfs.HelpIntfs
0034: FlatSB.FlatSB
0036: Printers.Printers
0047: GraphUtil.GraphUtil
0048: ExtCtrls.ExtCtrls
0051: ComCtrls.ComCtrls
0054: Dialogs.Dialogs
0055: Clipbrd.Clipbrd
0057: Forms.Forms
0058: JclResources.JclResources
0059: JclBase.JclBase
0061: JclWin32.JclWin32
0063: ComObj.ComObj
0064: AnsiStrings.AnsiStrings
0065: JclLogic.JclLogic
0066: JclStringConversions.JclStringConversions
0067: JclCharsets.JclCharsets
0068: Jcl8087.Jcl8087
0073: JclIniFiles.JclIniFiles
0074: JclSysInfo.JclSysInfo
0075: JclUnicode.JclUnicode
0076: JclWideStrings.JclWideStrings
0077: JclRegistry.JclRegistry
0078: JclSynch.JclSynch
0079: JclMath.JclMath
0080: JclStreams.JclStreams
0081: JclAnsiStrings.JclAnsiStrings
0082: JclStrings.JclStrings
0083: JclShell.JclShell
0084: JclSecurity.JclSecurity
0085: JclDateTime.JclDateTime
0086: JclFileUtils.JclFileUtils
0087: JclConsole.JclConsole
0088: JclSysUtils.JclSysUtils
0089: JclUnitVersioning.JclUnitVersioning
0090: JclPeImage.JclPeImage
0091: JclTD32.JclTD32
0092: JclHookExcept.JclHookExcept
0093: JclDebug.JclDebug
0094: MainUnit.MainUnit
Jamilla answered 2/11, 2010 at 12:5 Comment(4)
+1 That's pretty impressive, thanks for your effort! It's interesting to see the use of TJclMapParser. But unfortunately I am currently bound to Delphi 2009 and the easy RTTI is not available here :-/ But for everybody with D2010 this could be the way to go.Neelon
do you mean the InitUnits in the System unit?, because it's not exist in SysUtils.Ethnology
@Mohammed Nasman: Yes, I corrected it. (If you use the version with JclDebug/Mapfile there's no need to set the Breakpoint, the address will be read out of the Mapfile)Jamilla
@Heinrich Ulbricht: Just added a version for D2009 :DJamilla
R
8

For units in the interface uses list, the initialization sections of the units used by a client are executed in the order in which the units appear in the client's uses clause.

see Online Help \ Programs and Units \ The Initialization Section and this article: Understanding Delphi Unit initialization order

ICARUS computes the Runtime initialization order for its Uses Report:

This section lists the order in which the initialization sections are executed at runtime.

Rempe answered 2/11, 2010 at 10:17 Comment(3)
The part that makes it difficult is when Unit1 uses several other Units which may use even more Units.Jamilla
@Heinrich: Try ICARUS for the calculation of the runtime initialization order.Rempe
@Rempe Do you have any idea where ICARUS and Understanding Delphi Unit initialization order have moved to?Photojournalism
R
4

You might check out the unit System and SysInit and look for the procedure InitUnits. Here you see that every module compiled with Delphi has a list of units initialization and finalization pointers. Using those plus a map file might give you the exact initialization order, but it will take some pointer hackery.

Ruche answered 2/11, 2010 at 10:7 Comment(8)
+1 and don't forget to enable debug dcu's else you cannot set BP's in InitUnits.Jamilla
Btw it seems like InitContext.Module^.TypeInfo^.UnitNames contain an array of strings with the unitnames. If I cast it: PAnsiChar(InitContext.Module^.TypeInfo^.UnitNames) the result is (example):Jamilla
#8'Variants'#8'VarUtils'#7'Windows'#5'Types'#7'SysInit'#6'System'#8'SysConst'#8'SysUtils'#9'Character'#9'RTLConsts'#4'Math'#8'StrUtils'#8'ImageHlp'#8'MainUnit'#$B'JwaWinNetWk'#$A'JwaWinType'#8'JwaWinNT'#$E'JwaWinDLLNames'#$B'JwaWinError'#8'StdCtrls'#6'Dwmapi'#7'UxTheme'#8'SyncObjs'#7'Classes'#7'ActiveX'#8'Messages'#7'TypInfo'#8'TimeSpan'List'#7'Contnrs'#9'GraphUtil'#4'ZLib'#9'ListActns'#8'ExtCtrls'#7'Dialogs' and so onJamilla
Great, You won't even need a mapfile at all!, just hack out the shortstrings from a PAnsiChar and you're done.Ruche
@Jamilla This would be great! I am currently spelunking there. Which version of Delphi did you test this with? Currently I am not able to get the TypeInfo from the Module^.Neelon
I tested with D2010: Enable Debug DCU's and set BP on first line of SysUtils.InitUnits procedure.Jamilla
then I used: PAnsiChar(InitContext.Module^.TypeInfo^.UnitNames)Jamilla
It's all hardly readable here, I'll post it as a seperate answerJamilla
P
0

How about adding

OutputDebugString('In MyUnit initialization'); 

to the initialization sections?

Pernik answered 2/11, 2010 at 9:48 Comment(1)
Unfortunately I cannot change all units. Many of them are not under my control.Neelon
H
0

You can set breakpoints on all initialization sections that don't break but write a message to the debugger log. It will give you the same list as adding OutputDebugString('...') calls but without having to modify the source code of all units.

Honeyman answered 2/11, 2010 at 10:11 Comment(1)
I try to avoid this as in my opinion it is error-prone and a lot of work. If I have possibly hundreds of dependent units I would have to set a break point everywhere. And what if I only have a dcu and no source code? And what if I forget some unit? And after the next Delphi crash all breakpoints are gone. It would be nice if there was an easier solution.Neelon

© 2022 - 2024 — McMap. All rights reserved.