使用控制台应用程序在关闭时报告内存泄漏

11

我创建了一个控制台应用程序,并设置ReportMemoryLeaksOnShutdown := True。

我创建了一个TStringList但没有释放它。

当程序执行完成时,我会看到短暂的内存泄漏,但然后控制台就关闭了。

我尝试在结尾添加ReadLn;,但这只会显示一个空白的控制台窗口,这是有道理的。

我需要找到一种方法,在内存泄漏报告之后但完全关闭程序之前暂停执行。

我正在使用Delphi 10 Seattle。

program Project1;

{$APPTYPE CONSOLE}

uses
  System.Classes,
  System.SysUtils;

var
  s : TStringList;

begin
  try
    ReportMemoryLeaksOnShutdown := True;
    s := TStringList.Create;

    //ReadLn doesn't work here, which makes sense.
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  //I need to be able to pause the program somewhere after the end statement here.
end.

7
在之前已经打开的命令行窗口中运行程序,而不是在集成开发环境(IDE)中运行。 - Tom Brunberg
2
你可以尝试使用我的AutoConsole单元,详情请见这里:http://rvelthuis.blogspot.de/2016/07/new-velthuisautoconsole-unit.html。只需将其添加到您的控制台程序中即可。 - Rudy Velthuis
1
在 "system.end" 上添加一个断点。 - Sertac Akyuz
1
就我所知,在XE2中,我必须按“确定”按钮才能关闭对话框 - System.ScanForMemoryLeaks调用MessageBoxA会阻塞执行。 - Sertac Akyuz
@SertacAkyuz 在柏林,system.end. 上的断点也可以正常工作。 - Tom Brunberg
1
@SertacAkyuz,他们真的在后来的Delphi版本中将其“优化”掉了。 :-/ - Arioch 'The
5个回答

11

最简单的方法是在之前打开的命令窗口中运行应用程序。

如果您坚持在IDE中运行时查看内存泄漏报告,请按以下步骤操作:

  • 在 GetMem.inc 中找到 ShowMessage 过程(在 Delphi 10 Seattle 中为第 4856 行)
  • 在该过程的 end; 上设置断点。

或者,如 Sertac Akyuz 的评论所述,在 system 单元的 end. 上设置断点。

您还可以将内存泄漏报告重定向到文件。从以下链接下载 FastMM 的完整版本:

https://sourceforge.net/projects/fastmm/

或者更好的选择是,从 Arioch 'The 处获取:

https://github.com/pleriche/FastMM4

并在 FastMM4Options.inc 中设置所需选项。


2
这是一个有点老的URL,新的是github.com/pleriche/FastMM4。 - Arioch 'The
他也可以将其重定向到Windows Debug Strings工具,但我认为这不是他问题的关键。 - Arioch 'The

8
var
  SaveExitProcessProc: procedure;
  s: TStringList;

procedure MyExitProcessProc;
begin
  ExitProcessProc := SaveExitProcessProc;
{$I-}
  ReadLn;
{$I+}
end;

begin
  SaveExitProcessProc := ExitProcessProc;
  ExitProcessProc := MyExitProcessProc;
  ReportMemoryLeaksOnShutdown := True;
  s := TStringList.Create;
end.

1
错误可能是因为在管理器完成后尝试获取readln内存引起的。在Windows上可以用消息框替换。 - Sertac Akyuz
1
@Arioch - 它是用来替代readln的,以停止执行 - 没有什么可复制的。其次,你错了,按下标准API消息框上的ctrl+c键,标题、文本和按钮文本将被复制到剪贴板中。 - Sertac Akyuz
@SertacAkyuz 或许是在后来的 Windows 版本中添加的。相当长一段时间内,这种复制只适用于 VCL messagebox。 - Arioch 'The
1
@Arioch - 我刚刚测试过并确认它在XP上可行。我相当确定它也适用于2K,但我不再有2K的虚拟机了。 - Sertac Akyuz
1
实际上,情况恰恰相反。Windows 最先在 Windows 2000 beta 版本之一中为消息框添加了复制行为。然后,在 RAID(早于 QC)中提出了一个建议,要为 VCL 添加复制行为,因为测试人员厌倦了添加截图,然后输入文本以便能够搜索线程。 - Jeroen Wiert Pluimers
显示剩余4条评论

6
这是最近Delphi版本中的一个错误。我刚在最近的免费Delphi 10.1 Starter上检查过,它的行为与您描述的一样 - 但由于它没有提供RTL源代码,我无法检查确切的原因。
在Delphi XE2中,它的行为符合预期:创建任务模态对话框并等待用户响应,就像Sertak所描述的那样。
在Delphi 10.1中,内存泄漏确实被报告到控制台窗口,但程序不会停止等待用户处理。这是一个糟糕的解决方案,因为它可能被用于脚本(CMD或PS脚本可能会将此消息与合法输出混淆并导致后续程序执行失败)。
我认为您需要在Delphi 10.0上开启回归型错误报告 - 但我认为他们在10.2版发布之前不会修复它。
我还把您的应用程序从Delphi分叉的内存管理器切换到原始管理器,然后错误行为被撤销了:程序显示了消息框,并在退出到IDE之前等待我关闭它。
目前,我建议您使用上述原始内存管理器而非其分叉版本。
program Project1;

{$APPTYPE CONSOLE}

uses
  FastMM4,
  System.Classes,
  System.SysUtils;
...

原始内存管理器位于http://github.com/pleriche/FastMM4。 您可以在Delphi中使用Git客户端或独立的Git客户端来保持更新,或者只下载一次代码并停止更新,由您决定。

相关代码摘录如下:

  {$ifdef LogErrorsToFile}
     {Set the message footer}
      LMsgPtr := AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
      {Append the message to the memory errors file}
      AppendEventLog(@LLeakMessage[0], UIntPtr(LMsgPtr) - UIntPtr(@LLeakMessage[1]));
  {$else}
      {Set the message footer}
      AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
  {$endif}
  {$ifdef UseOutputDebugString}
      OutputDebugStringA(LLeakMessage);
  {$endif}
  {$ifndef NoMessageBoxes}
      {Show the message}
      AppendStringToModuleName(LeakMessageTitle, LMessageTitleBuffer);
      ShowMessageBox(LLeakMessage, LMessageTitleBuffer);
  {$endif}
    end;
  end;
{$endif}
end;

并且

{Shows a message box if the program is not showing one already.}
procedure ShowMessageBox(AText, ACaption: PAnsiChar);
begin
  if (not ShowingMessageBox) and (not SuppressMessageBoxes) then
  begin
    ShowingMessageBox := True;
    MessageBoxA(0, AText, ACaption,
      MB_OK or MB_ICONERROR or MB_TASKMODAL or MB_DEFAULT_DESKTOP_ONLY);
    ShowingMessageBox := False;
  end;
end;

这段代码依赖于在桌面Windows上运行,因此Embarcadero可能尝试“修复”它以使其跨平台。然而,他们这样做使其在Windows控制台上出现故障......此外,考虑使用其他形式的日志记录 - 将其写入文件中和/或将其写入Windows调试字符串中。它们不会像模态窗口那样吸引注意力,但至少可以帮助您保存信息,如果您知道在哪里查找它们的话。

这并不是一个真正的答案,它不能像期望的那样暂停控制台程序。这更像是对Delphi中一个错误的评论。 - oOo
@oOo 嗯,也许吧。但是SO并没有提供除提交非被接受的答案之外添加资源性评论的方法。这就是SO的方式或者说高速公路。 - Arioch 'The

6

这确实是一种黑客技巧,请勿在生产环境中使用 :)

ReportMemoryLeaksOnShutdown:= True;
IsConsole:= False;
TStringList.Create;

然而,这会导致泄漏消息(以及其他一些消息)在消息框中显示(按Ctrl + C可复制所有文本)。 < p > < em >(已在Delphi 10.2上测试,请报告任何我们不喜欢的副作用)


1
这是一个完美的解决方案,可以回答这个问题!只需添加 IsConsole := false;,就会显示一个带有内存泄漏信息的消息框。 - Xalo
1
这在 Delphi 10.3 和 Windows 7 Home Edition 上确实有效,谢谢!=D - oOo
它会在控制台程序/应用程序最可能在清理单元/完成部分期间暂停,因此它可能取决于退出例程的顺序等,最有可能是用户单元将首先被清理,但如果退出过程被覆盖,则非常可能如果内存泄漏发生在其中,则可能不再捕获它,然后再次可能会,不确定内存管理器是否仍处于活动状态/在线退出过程中,要测试的组合太多了,现在我就把它留在这里。 - oOo

0

在system.pas中的“end.”上设置断点。

然而,这个解决方案并不完全理想,因为退出过程/单元终结仍将在此“end.”语句之后执行。

可以通过F7/调试/步入“end.”语句来“检查”它,它将导致一些汇编函数,并且一旦通过F8跨越汇编指令退出汇编函数,它将返回到system.pas中的一个名为“FinalizeUnits”的函数,该函数递归调用自身以清除我认为是单元的终止部分。

因此,只要您不必在清理单元的终止部分后暂停,这个解决方案就不会太糟糕。但是,单元/终止部分的清理遵循某种顺序,很可能在“end.”语句中关闭内存管理器之前,将执行您自己的单元的终止部分。

否则,必须使用不同的解决方案。

要进入system.pas,请将其暂时添加到uses子句中,然后选择打开文件,稍后将其删除以防止编译错误,例如:“[dcc32错误]TestProgram.dpr(8):E2004标识符重新声明:'System'”


1
是的,除了我们大多数人不使用 system.dcu 的调试版本之外,这也无法在 IDE 之外工作。 - Atys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接