Search code examples
formsdelphimemory-leaksvclfastmm

Memory Leak on Delphi Form Controlls


I've found a strange memory leak in a project that I've been newly assigned to.

On terminating, the program displays the following FastMM4 error message.

FastMM4 Error

The project uses BusinessSkinForm

TbsaSpeedButtonSubClass is from the third party BusinessSkinForm library, however the speed buttons on that form appear to be regular VCL form controls.

When I add another speed button to the form,

speedButton

I now have 25 instances of TbsaSpeedButtonSubClass that leak instead of 24.

Which leads me to think that the leak is due to the TSpeedButton. However, this seems strange to me since I would expect form components to be automatically freed by the form upon destruction.

Perhaps BusinessSkinForm does something unusual to the form which results in a leak...

I'm not sure how to get rid of this leak

EDIT

Thanks to KenWhite I have a memory leak report from FastMM4

Here it is on Pastebin

EDIT

As shown in the stack trace, the problem can be traced to a TMUSICMainForm.SkinForm_OnCreate(SkinForm: TForm);

The problem seems to be related to a BSA: TbsaSkinAdapter

If I comment out the line BSA.ChangeSkinData;

the leak is no longer present.

EDIT

Here is the important part of the stack trace

--------------------------------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 ~ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

EDIT

I've managed to create a minimum working example of the memory leak.

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.

I've found that even if I comment out seemingly innocent looking lines like BSF.BorderIcons:=[biMinimize,biMaximize]; or BSA.AdapterType := bsaUseClasses; then the memory leak disappears.

Note my delphi form in this example contains 1 TSpeedButton

minimal bug

EDIT

I should also add that I'm using windows 7 (64bit), 6GB ram, delphi 5, Business Skin version 4.70

EDIT

the bsaadapter unit in BusinessSkinForm contains a function

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;

It seems that speed buttons do not get freed because they are instances of TGraphicControl

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

While a regular TButton will get freed


Solution

  • From what I can see here there are a number of ways which I can proceed from here.

    1. Submit a fix to BusinessSkinForm to handle the speed button

    2. Live with using a work around ie: BSA.AdapterType := bsaUseNames; instead of BSA.AdapterType := bsaUseClasses;

    3. Replace the speed buttons with the regular TButton.