为了调试我们的慢启动Delphi XE3应用程序,我们想记录系统中所有使用过的单元的初始化阶段。
要完成此任务,我们需要每个初始化调用的单位名称。
分析数据,当单步执行 InitNames 时,我们会在里面找到第一个单位名称:
InitContext.InitTable^.TypeInfo.UnitNames
但在调用初始化过程之前,我们不知道如何从单元ID I 中获取相应的名称。 代码文档说,TypeInfo.UnitNames包含所有单位的连接单位名称..但我如何在它们之间旅行?它不是一个数组,也不是一个带分隔符的长字符串。
代码,我想插入日志例程。
procedure InitUnits;
var
Count, I: Integer;
Table: PUnitEntryTable;
P: Pointer;
begin
if InitContext.InitTable = nil then
exit;
Count := InitContext.InitTable^.UnitCount;
I := 0;
Table := InitContext.InitTable^.UnitInfo;
{$IFDEF LINUX}
Inc(PByte(Table), InitContext.Module^.GOT);
{$ENDIF}
try
while I < Count do
begin
/////////////////////////////////////
MyLogCode( 'Unit: ' + Get UnitName here )
/////////////////////////////////////
P := Table^[I].Init;
Inc(I);
InitContext.InitCount := I;
if Assigned(P) and Assigned(Pointer(P^)) then
begin
{$IF defined(MSWINDOWS)}
TProc(P)();
{$ELSEIF (defined(POSIX) and defined(CPUX86))}
CallProc(P, InitContext.Module^.GOT);
{$ELSE}
TProc(P)();
{$ENDIF}
end;
end;
except
FinalizeUnits;
raise;
end;
end;
重新编译System.pas将通过Arnaud Bouchez suggested solution完成。
答案 0 :(得分:3)
UnitNames
包含多个单位名称。它们是Pascal短串,连接在一起。但是,正如我们将要看到的,它们不是您需要的名称。
在InitUnits
中打破调试器并评估:
PAnsiChar(InitContext.InitTable.TypeInfo.UnitNames)
在我的简单测试项目中,只使用SysUtils
的控制台应用,您会看到以下内容:
#$F'System.SysUtils'#6'System'#$18'System.Internal.ExcUtils'#$F'System.SysConst' #7'SysInit'#$10'System.Character'#$E'Winapi.Windows'#$E'System.UITypes' #$C'System.Types'#$10'System.RTLConsts'#$C'Winapi.PsAPI'#$F'Winapi.SHFolder' #$F'Winapi.ImageHlp‹À'
第一个字符是字符串的长度。它们是一个接一个地连接在一起,总共有InitContext.InitTable.TypeInfo.UnitCount
个名字。对于我的简单项目InitContext.InitTable.TypeInfo.UnitCount
,评估为13。
但是,这些名称与初始化的单位不对应。在我的测试项目中,InitContext.InitTable^.UnitCount
的值为18,单位的初始化顺序与上面列出的顺序完全不同。我相信你知道,SysInit
永远是第一位的。从上面可以看出,它位于列表的中间。因此,虽然InitContext.InitTable.TypeInfo.UnitNames
给出了某些单位的列表,但它与需要初始化的单位和初始化顺序无关。
所以当我读到它时,UnitNames
无法帮助你。我的信念是你需要使用详细的地图文件来解码它。您需要查找函数Table^[I].Init
的名称。例如,如果你使用madExcept,这很容易做到。
当然,您可能无法在InitUnits
内执行查找。你面临鸡和鸡蛋的情况。在开始记录之前,您可能需要至少初始化一些单元。
例如,看起来您正在尝试分配字符串变量。这将失败,因为RTL堆分配器尚未初始化。如果您希望在RTL初始化之前调用它,则您的日志记录代码无法使用RTL堆执行任何动态分配。
这对我来说似乎都是最重要的。如果我是你,我会:
I
。TProc(P)()
的调用。P
以找出它所附加的单位。答案 1 :(得分:0)
通常,最好不要依赖类构造函数,初始化部分,类析构函数和终结部分的顺序。
在一个大型(1.2M LOC)项目中,人们将无法推断出Delphi将使用的订单是什么。
最好重构代码以避免此类依赖或使用Singleton模式副作用(首次需要时初始化)。
然而,如果你真的必须输出订单,你可以手动记录所有
初始化部分条目
最终确定部分条目
类构造函数
类析构函数
一种安全的方法是使用Windows API - OutputDebugString()
您可以使用以下单元来帮助您进行日志记录
unit unt_Debug;
{$SCOPEDENUMS ON}
interface
uses
// System
System.SysUtils
{$IFDEF MACOS}
,FMX.Types
{$ELSE}
,Winapi.Windows
{$ENDIF};
{$WARN SYMBOL_PLATFORM OFF}
/// <remarks>
/// Output debug string. Output debug string can be seen in Delphi
/// View|Debug Windows|Event Log or with 3-rd party programs such as
/// dbgview.exe from SysInternals (www.sysinternals.com)
/// </remarks>
procedure ODS(const Text: string);
procedure ReportClassConstructorCall(const C: TClass);
procedure ReportClassDestructorCall(const C: TClass);
procedure ReportInitializationSection(const UnitName: string);
procedure ReportFinalizationSection(const UnitName: string);
implementation
procedure ReportClassConstructorCall(const C: TClass);
begin
ODS(Format('%0:s class constructor invoked.', [C.ClassName]));
end;
procedure ReportClassDestructorCall(const C: TClass);
begin
ODS(Format('%0:s class destructor invoked.', [C.ClassName]));
end;
procedure ReportInitializationSection(const UnitName: string);
begin
ODS(Format('Unit %0:s - entering Initialization section.', [UnitName]));
end;
procedure ReportFinalizationSection(const UnitName: string);
begin
ODS(Format('Unit %0:s - entering Finalization section.', [UnitName]));
end;
procedure ODS(const Text: string);
begin
{$IFDEF DEBUG}
{$IFDEF MACOS}
// http://stackoverflow.com/questions/12405447/outputdebugstring-with-delphi-for-macosunit unt_Debug;
Log.d(Text);
{$ENDIF}
{$IFDEF LINUX}
__write(stderr, AText, Length(AText));
__write(stderr, EOL, Length(EOL));
{$ENDIF}
{$IFDEF MSWINDOWS}
OutputDebugString(PWideChar(Text));
{$ENDIF}
{$ENDIF}
end;
{$WARN SYMBOL_PLATFORM ON}
end.
它可以这样使用:
unit Sample;
interface
type
TSample = class
public
class constructor Create;
class destructor Destroy;
end;
implementation
uses
unt_Debug;
class constructor TSample.Create;
begin
ReportClassConstructorCall(TSample);
// Do stuff
end;
class destructor TSample.Destroy;
begin
ReportClassDestructorCall(TSample);
// Do stuff
end;
initialization
ReportInitializationSection(TSample.UnitName);
// do stuff
finalization
ReportFinalizationSection(TSample.UnitName);
// do stuff
end.