New和Dispose在内部做了什么?

时间:2016-01-18 12:19:02

标签: delphi

我想知道在调用New和Disposed时内部会发生什么。 Delphi的帮助提供了一个合理的解释,但是如果一个人会写一个自己的New和Dispose,那该怎么办呢?哪个方法在内部调用两个方法还是全部组装?

我不打算写自己的New和Dispose。我对这两种方法的内部工作非常好奇。

2 个答案:

答案 0 :(得分:9)

New执行以下操作:

  1. 通过调用GetMem
  2. 为新对象分配内存
  3. 初始化任何托管字段,如字符串,接口,动态数组等。
  4. Dispose推翻了这个:

    1. 完成托管字段。
    2. 通过调用FreeMem来取消分配内存。
    3. 请注意,NewDispose都是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._FinalizeSystem._InitializeSystem._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操作码(如果有)。