【问题标题】:Can I determine the order in which my units have been initialized?我可以确定我的单位初始化的顺序吗?
【发布时间】:2011-05-03 20:22:55
【问题描述】:

我正在寻找一个可能与单元初始化顺序有关的错误。有没有办法查看哪个initialization 部分何时执行?我需要知道顺序。这是在调试过程中,所以我拥有 Delphi IDE 的全部功能,在我的例子中是 Delphi 2009。

我可以设置断点,但是当有很多单元时这相当乏味。

你有什么建议吗?

【问题讨论】:

  • 相关:如果您在接口部分使用一个单元,您知道该单元将在使用该单元的单元之前初始化。在实现部分中使用单位时,情况并非如此。所以通常当你使用一个在它的初始化部分创建的带有单例的单元时,你应该在接口部分使用该单元,以确保它在使用前被初始化。

标签: windows delphi debugging delphi-2009 desktop-application


【解决方案1】:

这是我刚刚在D2010中测试的一些代码,注意需要在System.InitUnits中设置一个Breakpoint并获取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 和 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;

在我的情况下输出:

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

【讨论】:

  • +1 这令人印象深刻,感谢您的努力!看到 TJclMapParser 的使用很有趣。但不幸的是,我目前绑定到 Delphi 2009 并且这里没有简单的 RTTI :-/ 但是对于使用 D2010 的每个人来说,这可能是要走的路。
  • 你的意思是系统单元中的 InitUnits 吗?因为它在 SysUtils 中不存在。
  • @Mohammed Nasman:是的,我更正了。 (如果你使用带JclDebug/Mapfile的版本就不需要设置Breakpoint,地址会从Mapfile中读出)
  • @Heinrich Ulbricht:刚刚为 D2009 添加了一个版本:D
【解决方案2】:

对于界面中的单位使用列表, 的初始化部分 客户端使用的单元在 单位出现的顺序 客户的 uses 子句。

参见在线帮助\Programs and Units\初始化部分和这篇文章:Understanding Delphi Unit initialization order

ICARUS 为其使用报告计算运行时初始化顺序

此部分列出了初始化部分在运行时执行的顺序。

【讨论】:

  • 造成困难的部分是当 Unit1 使用其他几个可能使用更多 Units 的 Units 时。
  • @Heinrich:试试ICARUS 计算运行时初始化顺序。
【解决方案3】:

您可以查看单元 System 和 SysInit 并查找过程 InitUnits。在这里,您可以看到使用 Delphi 编译的每个模块都有一个单元初始化和终结指针列表。使用这些加上一个映射文件可能会给你确切的初始化顺序,但它需要一些指针hackery。

【讨论】:

  • +1 并且不要忘记启用调试 dcu,否则您无法在 InitUnits 中设置 BP。
  • 顺便说一句,似乎 InitContext.Module^.TypeInfo^.UnitNames 包含带有单元名称的字符串数组。如果我投它: PAnsiChar(InitContext.Module^.TypeInfo^.UnitNames) 结果是(示例):
  • #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' 等等
  • 太好了,您甚至根本不需要地图文件!只需从 PAnsiChar 中删除短字符串即可。
  • @Remko 这太好了!我目前正在那里探险。您使用哪个版本的 Delphi 进行了测试?目前我无法从 Module^ 中获取 TypeInfo。
【解决方案4】:

添加怎么样

OutputDebugString('In MyUnit initialization'); 

到初始化部分?

【讨论】:

  • 很遗憾,我无法更改所有单位。其中许多不在我的控制之下。
【解决方案5】:

您可以在所有不会中断但将消息写入调试器日志的初始化部分设置断点。它将为您提供与添加 OutputDebugString('...') 调用相同的列表,但无需修改所有单元的源代码。

【讨论】:

  • 我尽量避免这种情况,因为我认为这很容易出错并且需要大量工作。如果我可能有数百个依赖单元,我将不得不在任何地方设置一个断点。如果我只有一个 dcu 而没有源代码怎么办?如果我忘记了某个单位怎么办?在下一次 Delphi 崩溃之后,所有断点都消失了。如果有更简单的解决方案就好了。
猜你喜欢
  • 1970-01-01
  • 2017-04-24
  • 2010-10-22
  • 2011-04-10
  • 1970-01-01
  • 2019-05-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多