Delphi 表单控件内存泄漏

Memory Leak on Delphi Form Controlls

我在新分配到的项目中发现了一个奇怪的内存泄漏。

终止时,程序显示以下 FastMM4 错误消息。

项目使用BusinessSkinForm

TbsaSpeedButtonSubClass 来自第三方 BusinessSkinForm 库,但是该窗体上的速度按钮似乎是常规的 VCL 窗体控件。

当我在表单中添加另一个速度按钮时,

我现在有 25TbsaSpeedButtonSubClass 实例泄漏,而不是 24 个。

这让我认为泄漏是由于 TSpeedButton。 然而,这对我来说似乎很奇怪,因为我希望表单组件在销毁时被表单自动释放。

也许 BusinessSkinForm 对表格做了一些不寻常的事情导致泄漏...

我不确定如何摆脱这个漏洞

编辑

感谢 KenWhite,我收到了来自 FastMM4 的内存泄漏报告

Here it is on Pastebin

编辑

如堆栈跟踪所示,问题可以追溯到 TMUSICMainForm.SkinForm_OnCreate(SkinForm: TForm);

问题似乎与BSA: TbsaSkinAdapter

有关

如果我注释掉 BSA.ChangeSkinData;

泄漏不再存在。

编辑

这是堆栈跟踪的重要部分

--------------------------------2015/11/24 12:16:03-------------------------------- A memory block has been leaked. The size is: 308

This block was allocated by thread 0x1258, and the stack trace (return addresses) at the time was: 402AB6 [madZip][madZip][@GetMem] 4035F9 [madCrypt][madCrypt][TObject.NewInstance] 4039CA [madCrypt][madCrypt][@ClassCreate] 67438A [bsaadapter.pas][bsaadapter][TbsaSpeedButtonSubclass.Create][11537] 66137E [bsaadapter.pas][bsaadapter][TbsaHook.SetControl][2637] 403A1E [madCrypt][madCrypt][@AfterConstruction] 665BFB [bsaadapter.pas][bsaadapter][TbsaSkinManager.DoControlMessage][4898] 6615B7 [bsaadapter.pas][bsaadapter][TbsaHookCollection.AddControl][2760] 404ACB [madExcept][madExcept][@LStrSetLength] 662A0E [bsaadapter.pas][bsaadapter][TbsaSkinManager.CollectSpeedButton][3544] 92C81D [Sources\uMainForm.pas][uMainForm][TMUSICMainForm.SkinForm_OnCreate][4778]

The block is currently used for an object of class: TbsaSpeedButtonSubclass

The allocation number is: 475863

Current memory dump of 256 bytes starting at pointer address 7E8A7670: 64 C9 65 00 00 00 00 00 00 00 00 00 00 00 00 00 68 F3 48 00 50 96 97 7E B8 5E 74 7E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B8 D1 74 7E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d É e . . . . . . . . . . . . . h ó H . P – — ~ ¸ ^ t ~ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¸ Ñ t ~ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

编辑

我已经设法创建了内存泄漏的最小工作示例。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  BusinessSkinForm, bsaadapter, Buttons
  ;

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  BSF: TbsBusinessSkinForm;
  BSA: TbsaSkinAdapter;
begin
    BSF := TbsBusinessSkinForm.Create(Self);

    BSF.BorderIcons:=[biMinimize,biMaximize];

    BSA := TbsaSkinAdapter.Create(Self);

    BSA.AdapterType := bsaUseClasses;

    BSA.ChangeSkinData;
end;

end.

我发现即使我注释掉看似无辜的线条 BSF.BorderIcons:=[biMinimize,biMaximize];BSA.AdapterType := bsaUseClasses; 那么内存泄漏就消失了。

注意我的 delphi 表单在这个例子中包含 1 TSpeedButton

编辑

我还应该补充一点,我正在使用 windows 7(64 位),6GB 内存,delphi 5,Business Skin 版本 4.70

编辑

BusinessSkinForm 中的 bsaadapter 单元包含一个函数

procedure TbsaSkinManager.DoUnhook(Control: TControl; Handle: HWnd);
var
  i: integer;
  SC: TbsaSubclass;
  R: TRect;
begin
  if FUnhooking then Exit;

  if FUnhookedList = nil then
  begin
    FUnhooking := true;
    Exit;
  end;

  FUnhooking := true;
  try
    for i := FHandleList.Count - 1 downto 0 do
    begin
      SC := TbsaSubclass(FHandleList[i]);

      if (Handle <> 0) and (SC.Handle = Handle) then
      begin
        R := Rect(0, 0, 2000, 2000);
        PostMessage(Handle, WM_NCPAINT, 0, 0);
        InvalidateRect(Handle, @R, false);
        FHandleList.Delete(i);
        SC.Free;
      end;

      if (Control <> nil) and (SC.Control = Control) then
      begin
        FHandleList.Delete(i);
        SC.FControl := nil;
        if not (Control is TGraphicControl) then
          SC.Free;
      end;
    end;
  finally
    FUnhooking := false;
  end;
end;

似乎速度按钮没有被释放,因为它们是 TGraphicControl

的实例

if not (Control is TGraphicControl) then SC.Free;

虽然常规 TButton 会被释放

从我在这里看到的情况来看,有很多方法可以从这里开始。

  1. 提交对 BusinessSkinForm 的修复以处理速度按钮

  2. 接受变通方法 即:BSA.AdapterType := bsaUseNames; 而不是 BSA.AdapterType := bsaUseClasses;

  3. 用常规 TButton 替换速度按钮。