【问题标题】:MakeScreenshot leaking?MakeScreenshot 泄露?
【发布时间】:2012-06-02 10:10:46
【问题描述】:

大家晚上好!

在当前的项目中,我遇到了相当令人担忧的内存泄漏,我似乎无法解决。

我让应用程序在标准使用情况下运行了一夜,当我在 8 小时后醒来时,它消耗了大约 750MB 的内存,而它开始时是大约 50MB。 Windows 任务管理器不适合检查泄漏,只能让您首先发现泄漏。

我已经清除了其他一些内存泄漏,其中主要与 Firemonkeys 的 TGlowEffect 有关。 ReportLeaksOnShutdown 没有检测到它,但它的内存使用在动态修改的对象(例如旋转或缩放变化)上变得非常过度。

我已经将它追踪到一个计时器(并禁用它完全阻止泄漏),如果可能的话,我需要帮助来修复它。

说明:此代码使用 Firemonkey MakeScreenshot 函数将 TPanel (SigPanel) 的视觉外观保存到 TMemoryStream。然后使用标准代码将此流数据上传到远程 FTP 服务器(见下文)。在SigPanel内部,有4个TLabelchildren,1个TRectanglechildren,6个TImagechildren。

注意事项CfId 是一个全局字符串,它基于一个随机的extended 浮点值生成,该浮点值随后与日期时间一起以yyyymmdd_hhnnsszzz 格式进行散列。这一生成是在创建表单时完成的,它会重复直到它得到一个有效的CfId(即不包含在 Windows 文件名中非法使用的字符)。一旦它得到一个有效的CfId,它就不再运行了(因为我不再需要生成一个新的 ID)。这让我几乎完全消除了重复CfId 的机会。

定时器中的代码如下;

var
  i : Integer;
  SigStream : TMemoryStream;
begin
  SigStream := TMemoryStream.Create;
  SigPanel.MakeScreenshot.SaveToStream(SigStream);
  SigPanel.MakeScreenshot.Free;
  if VT2SigUp.Connected then
  begin
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
  end else
  begin
    VT2SigUp.Connect;
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
  end;
    SigStream.Free;
end;

随着计时器 NOT 的运行,代码运行完全没有泄漏,ReportMemoryLeaksOnShutdown 确实 NOT 生成一条消息。启用计时器并允许至少“运行”一次,我得到了很多泄漏,这增加了计时器运行的次数。报告的泄漏如下;

Small Block Leaks

1 - 12 Bytes: Unknown x 1
13 - 20 Bytes: TList x 5, Unknown x 1
21 - 28 Bytes: TFont x 2, TGradientPoint x 8, TGradientPoints x 4, Unknown x 4
29 - 36 Bytes: TObjectList<FMX.Types.TCanvasSaveState> x 1, TBrushBitmap x 4,
TBrushGrab x 4, TPosition x 24, TGradient x 4, UnicodeString x1
37 - 44 Bytes: TBrushResource x 4
53 - 60 Bytes: TBrush x 4
61 - 68 Bytes: TBitmap x 5
69 - 76 Bytes: TD2DCanvasSaveState x 1
205 - 220 Bytes: TCanvasD2D x 1

Sizes of Medium and Large Block Leaks
200236

当计时器运行时,这些值将乘以 n 次(n 是计时器运行的次数)。中大块有 n 个值 200236(例如,如果计时器运行了 3 次,则为 200236, 200236, 200326)。

有趣的是,如果我删除与MakeScreenshot 关联的代码,则泄漏不再存在,并且内存使用率保持在正常水平。除了通常的内存使用外,没有任何异常,也没有报告泄漏。我已经尝试了多个代码示例,无论是保存到流并从那里上传,还是保存到流>文件然后上传文件,但函数本身似乎存在泄漏。一旦我发现这里有泄漏,我什至添加了MakeScreenshot.Free,但我似乎无法插入它,当然,我在我的一个代码“测试运行”中使用了try..finally

我什至使用 GDI+ 作为画布类型运行代码,并且在那里发生了相同的泄漏(唯一的变化是 D2D 泄漏引用了 GDI+)。

我非常感谢任何人对此进行的任何研究或笔记,此外,还有解决该问题的方法。

【问题讨论】:

  • 相信你刚刚发现FM内存泄漏(:
  • 我相信在你的应用初始化中设置 ReportMemoryLeaksOnShutdown := True; 应该可以告诉你什么是泄漏的......
  • @DorinDuminica 我相信是的。但是,我相信我发现问题在于FMX.Types.MakeScreenshot 中的Result 实际上并没有被释放。他们只是打电话给Result.Canvas.EndScene 并且永远不会释放它! @JerryDodge 这就是我跑去找出泄漏的原因,因为如果没有它我将无法获得精确的列表:)
  • 抱歉,还没看完全文就发布了
  • 目前正在验证释放结果是必需的修复,并且它不会导致任何奇怪的错误弹出。将在几个小时内发布答案。我希望它是需要的,但我想让它运行一段时间以确保它。到目前为止,结果看起来很有希望,因为内存使用情况保持稳定。

标签: delphi memory-leaks delphi-xe2 firemonkey


【解决方案1】:

我尝试在fmx中使用它,但它不起作用所以我从FMXexpress中取了一个类似的功能,它也是内存泄漏并对其进行了修改。

我将函数转换为传递 2 个参数的过程。第一个是您想要生成屏幕的对象,第二个是目标对象。最后它会清除内存。

class Procedure MakeScaleScreenshot(Sender: TControl; SetImg: TImage);

Class Procedure MakeScaleScreenshot(Sender: TControl; SetImg: TImage);
var
  fScreenScale: Single;
  Result:TBitmap;
function GetScreenScale: Single;
  var
    ScreenService: IFMXScreenService;
  begin
    Result := 1;
    if TPlatformServices.Current.SupportsPlatformService(IFMXScreenService,
      IInterface(ScreenService)) then
    begin
      Result := ScreenService.GetScreenScale;
    end;
  end;

begin    
  fScreenScale := GetScreenScale;
  Result := TBitmap.Create(Round(Sender.Width * fScreenScale), Round(Sender.Height * fScreenScale));
  Result.Clear(0);

  if Result.Canvas.BeginScene then
  try
    Sender.PaintTo(Result.Canvas, RectF(0, 0, Result.Width, Result.Height));
  finally
    Result.Canvas.EndScene;
    SetImg.Bitmap:=Result;
  end;

 Result.FreeHandle;
 Result.DisposeOf;

end;

//how to use

Procedure
Begin
  {TControl source screen}, {Img destiny of the TImage type}
  MakeScaleScreenshot(Image1,Image2);
end;

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

您没有释放MakeScreenshot 创建的位图。

procedure TForm1.Button1Click(Sender: TObject);
var
  ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;
  Panel1.MakeScreenshot.SaveToStream(ms);
  ms.Free;
end;

上面的代码没有保留对创建的位图的引用,因此没有机会释放它。而是像下面这样更改您的设计:

procedure TForm1.Button2Click(Sender: TObject);
var
  ms: TMemoryStream;
  bmp: TBitmap;
begin
  ms := TMemoryStream.Create;
  bmp := Panel1.MakeScreenshot;
  bmp.SaveToStream(ms);
  ms.Free;
  bmp.Free;
end;


使用下面的代码,您实际上是在创建两个位图并释放其中一个。

  SigPanel.MakeScreenshot.SaveToStream(SigStream);
  SigPanel.MakeScreenshot.Free;


最后,您的代码会更像下面这样:

var
  i : Integer;
  Bmp: TBitmap;
  SigStream : TMemoryStream;
begin
  SigStream := TMemoryStream.Create;
  try
    Bmp := SigPanel.MakeScreenshot;
    try
      Bmp.SaveToStream(SigStream);
      if not VT2SigUp.Connected then
        VT2SigUp.Connect;
      VT2SigUp.Put(SigStream, 'Sig_'+CfId+'.png', False);
    finally
      Bmp.Free;
    end;
  finally
    SigStream.Free;
  end;
end;

【讨论】:

  • 补充@Sertac answear 不要忘记使用 try finally 以避免放置错误并避免内存流泄漏,因为如果引发任何异常,将不会调用 SigStream.Free。
  • 啊,果然如此。这似乎是一个非常冗长的方法,最初看起来好像结果没有在FMX.Types 中释放。我假设MakeScreenshot 必须被调用并给出一个引用(例如一个流),但是看到你是如何做到的确实让我理解了如何正确使用该函数。正如我所说,我确实在我的一个代码修复中使用了try..finally,但为了解决这个问题,我把它拿出来尝试尽可能简化代码。
  • @Scott - 在此处发布问题或进行初始设计时,可以忽略错误处理。当我看到 cmets 上提到它时,我只是不想忽略它。
猜你喜欢
  • 1970-01-01
  • 2015-12-17
  • 2015-03-10
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多