Могу ли я определить порядок инициализации моих модулей?

Я ищу ошибку, которая может быть связана с порядком инициализации устройства. Есть ли способ узнать, какой initialization раздел был выполнен и когда? Мне нужно знать порядок. Это во время отладки, поэтому у меня есть все возможности Delphi IDE, в моем случае Delphi 2009.

Я мог бы установить точки останова, но это довольно утомительно, когда у меня много модулей.

Есть ли у вас какие-либо предложения?


person Heinrich Ulbricht    schedule 02.11.2010    source источник
comment
Связанный: Если вы используете модуль в разделе интерфейса, вы знаете, что этот модуль будет инициализирован ДО того, как модуль, который использует этот модуль. При использовании единиц в разделе реализации это не так. Поэтому обычно, когда вы используете модуль с синглтоном в нем, созданный в разделе инициализации, вы должны использовать этот модуль в разделе интерфейса, чтобы убедиться, что он инициализирован перед использованием.   -  person Ritsaert Hornstra    schedule 02.11.2010


Ответы (5)


Для модулей в интерфейсе uses list разделы инициализации модулей, используемых клиентом, выполняются в том порядке, в котором модули появляются в предложении uses клиента.

см. интерактивную справку \ Программы и модули \ Раздел инициализации и эта статья: Общие сведения об инициализации модуля Delphi заказ

ICARUS вычисляет порядок инициализации среды выполнения для его Отчет об использовании:

В этом разделе указан порядок, в котором разделы инициализации выполняются во время выполнения.

person splash    schedule 02.11.2010
comment
Часть, которая усложняет задачу, - это когда Unit1 использует несколько других единиц, которые могут использовать еще больше единиц. - person Remko; 02.11.2010
comment
@Heinrich: попробуйте ICARUS для расчета порядка инициализации среды выполнения. - person splash; 02.11.2010

Вот код, который я только что протестировал в D2010, обратите внимание, что вам нужно установить точку останова в System.InitUnits и получить адрес InitContext var (@InitContext). Затем измените CtxPtr, чтобы он имел этот адрес ВО ВРЕМЯ РАБОТЫ. (Может кто знает способ поумнее для этого).

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: а вот версия, использующая JclDebug и файл карты:

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;

Вывод в моем случае:

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: А вот версия для D2009 (требуется 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.

Вывод в моей системе (Unitname.Unitname на самом деле 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
person Remko    schedule 02.11.2010
comment
+1 Впечатляет, спасибо за ваши усилия! Интересно увидеть использование TJclMapParser. Но, к сожалению, в настоящее время я привязан к Delphi 2009, и простой RTTI здесь недоступен: - / Но для всех с D2010 это может быть подходящим вариантом. - person Heinrich Ulbricht; 02.11.2010
comment
Вы имеете в виду InitUnits в системном модуле? потому что его нет в SysUtils. - person Mohammed Nasman; 02.11.2010
comment
@ Мохаммед Насман: Да, я поправил. (Если вы используете версию с JclDebug / Mapfile, нет необходимости устанавливать точку останова, адрес будет считан из Mapfile) - person Remko; 02.11.2010
comment
@Heinrich Ulbricht: только что добавил версию для D2009: D - person Remko; 02.11.2010

Вы можете проверить модуль System и SysInit и найти процедуру InitUnits. Здесь вы видите, что каждый модуль, скомпилированный с помощью Delphi, имеет список указателей инициализации и завершения модулей. Их использование плюс файл карты может дать вам точный порядок инициализации, но это потребует некоторого взлома указателя.

person Ritsaert Hornstra    schedule 02.11.2010
comment
+1 и не забудьте включить debug dcu, иначе вы не сможете установить BP в InitUnits. - person Remko; 02.11.2010
comment
Кстати, кажется, что InitContext.Module ^ .TypeInfo ^ .UnitNames содержит массив строк с именами модулей. Если я использую его: PAnsiChar (InitContext.Module ^ .TypeInfo ^ .UnitNames), результат будет (пример): - person Remko; 02.11.2010
comment
# 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' и т. Д. - person Remko; 02.11.2010
comment
Отлично, вам вообще не понадобится файл карты !, просто вырежьте короткие строки из PAnsiChar, и все готово. - person Ritsaert Hornstra; 02.11.2010
comment
@Remko Это было бы здорово! Я сейчас там исследую. С какой версией Delphi вы это тестировали? В настоящее время я не могу получить TypeInfo из модуля ^. - person Heinrich Ulbricht; 02.11.2010
comment
Я тестировал с D2010: включить Debug DCU и установить BP в первой строке процедуры SysUtils.InitUnits. - person Remko; 02.11.2010
comment
затем я использовал: PAnsiChar (InitContext.Module ^ .TypeInfo ^ .UnitNames) - person Remko; 02.11.2010
comment
Здесь все плохо читается, выложу как отдельный ответ - person Remko; 02.11.2010

Как насчет добавления

OutputDebugString('In MyUnit initialization'); 

в разделы инициализации?

person David    schedule 02.11.2010
comment
К сожалению, я не могу поменять все единицы. Многие из них мне неподконтрольны. - person Heinrich Ulbricht; 02.11.2010

Вы можете установить точки останова на всех разделах инициализации, которые не прерываются, а записывают сообщение в журнал отладчика. Это даст вам тот же список, что и при добавлении OutputDebugString('...') вызовов, но без изменения исходного кода всех модулей.

person Otherside    schedule 02.11.2010
comment
Я стараюсь избегать этого, поскольку, на мой взгляд, это чревато ошибками и требует много работы. Если бы у меня были, возможно, сотни зависимых юнитов, мне бы пришлось везде устанавливать точку останова. А что, если у меня есть только dcu и нет исходного кода? А что, если я забуду какой-то блок? И после следующего сбоя Delphi все точки останова исчезнут. Было бы неплохо, если бы было решение попроще. - person Heinrich Ulbricht; 02.11.2010