我想知道在调用New和Disposed时内部会发生什么。 Delphi的帮助提供了一个合理的解释,但是如果一个人会写一个自己的New和Dispose,那该怎么办呢?哪个方法在内部调用两个方法还是全部组装?
我不打算写自己的New和Dispose。我对这两种方法的内部工作非常好奇。
答案 0 :(得分:9)
New
执行以下操作:
GetMem
Dispose
推翻了这个:
FreeMem
来取消分配内存。请注意,New
和Dispose
都是intrinsic functions。这意味着编译器具有额外的知识,并且能够根据所讨论的类型改变它们的实现方式。
例如,如果类型没有托管字段,则New
会优化为对GetMem
的简单调用。如果类型包含托管字段,则New
通过调用System._New
来实现,Dispose
执行上述步骤。
FreeMem
的实施大致相同。对非托管类型的System._Dispose
进行简单调用,否则调用System._New
。
现在,function _New(Size: NativeInt; TypeInfo: Pointer): Pointer;
begin
GetMem(Result, Size);
if Result <> nil then
_Initialize(Result, TypeInfo);
end;
实现如下:
PUREPASCAL
请注意,为简单起见,我刚刚展示了GetMem
变体。对System._Initialize
的调用很简单。对TypeInfo
的调用涉及更多。它使用System._Dispose
参数来查找对象中包含的所有托管类型并初始化它们。这是一个递归过程,因为,例如,记录可能包含本身是结构类型的成员。您可以在RTL源中查看所有血腥细节。
至于System._Finalize
,它会调用FreeMem
,然后调用System._Finalize
。 System._Initialize
与System._Initialize
非常相似,只是它最终确定了托管类型而不是初始化它们。
对于性能敏感的Delphi用户而言,长期以来一直存在这样的问题:System._Finalize
和 public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.EXACTLY);
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
listItem.measure(desiredWidth, 0);
totalHeight += listItem.getMeasuredHeight();
}
System.out.println("list item height setListViewHeightBasedOnChildren :"+totalHeight);
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
以这种方式实现,在运行时类型信息之上。这些类型在编译时是已知的,编译器可以编写为内联初始化和最终化,这将导致更好的性能。
答案 1 :(得分:4)
如何编写自己的New()
和Dispose()
函数,并使用自己的记录初始化/完成:
uses
TypInfo;
procedure RecordInitialize(Dest, TypeInfo: pointer);
asm
{$ifdef CPUX64}
.NOFRAME
{$endif}
jmp System.@InitializeRecord
end;
procedure RecordClear(Dest, TypeInfo: pointer);
asm
{$ifdef CPUX64}
.NOFRAME
{$endif}
jmp System.@FinalizeRecord
end;
function NewRec1(TypeInfo: pointer): pointer;
begin
GetMem(result, GetTypeData(TypeInfo)^.RecSize);
RecordInitialize(result, TypeInfo);
end;
function NewRec2(TypeInfo: pointer): pointer;
var
len: integer;
begin
len := GetTypeData(TypeInfo)^.RecSize;
GetMem(result, len);
FillChar(result^, len, 0);
end;
procedure NewDispose(Rec: pointer; TypeInfo: Pointer);
begin
RecordClear(Rec, TypeInfo);
FreeMem(Rec);
end;
首先,有一个低级别的asm技巧来调用我们需要的隐藏的内部函数。
然后,我提出了两种初始化记录的方法,一种使用System.@InitializeRecord
,另一种使用FillChar
。另请注意,如果在动态数组中分配记录,则项目的初始化/完成将自动完成。初始化不会调用Initialize()
但FillChar
会用零填充内存。
遗憾的是,我们无法使用泛型参数定义全局函数/过程,否则我们可能会删除TypeInfo()
参数。
几年前,我重写了RTL part of record initialization/finalization,以加快执行速度。请注意,TObject
会调用FinalizeRecord
,因此它是RTL的一部分,可以获得优化。编译器应该发出代码而不是使用RTTI - 但至少应该优化RTL。
如果您使用我们的Open Source SynCommons.pas
单元,并为您的项目定义DOPATCHTRTL
条件,您将拥有RTL FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance
低级函数的进程内补丁,这将使用优化的asssembly和SSE2操作码(如果有)。