Search code examples
delphifastmm

How to properly free a Delphi form


Trying to fix memory leaks in my application using FastMM in full debug mode, some leaks are reported regarding instances of (descendants of) TForm. After checking and stepping in the code, I can say for sure that those forms are released and not freed. It looks as if FastMM is checking for leaks before the actual freeing scheduled by Release happens.

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Release; // FastMM reports MyForm as a leak

I tried freeing them instead, and those memory leaks are no longer being reported, but sometimes, an access violation occurs when freeing:

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Free; // // FastMM does not report MyForm as a leak, but sometimes an access violation is triggered

I tried releasing them, and call Application.ProcessMessages immediately after, as I understood could be done reading Zoë Peterson's answer in https://stackoverflow.com/a/917187/1465896. I must have understood wrong, because it crashes always immediately in Application.ProcessMessages:

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Release; 
Application.ProcessMessages; // always triggers an access violation 

I don't want to register those forms as expected memory leaks, because they contain more objects that clutter the memory leak log file, making it harder to find more important leaks.

So my question is, how to properly free a Delphi form so that FastMM won't report it as a leak?

As requested, here is an SSCnCE (Short, Self Contained, Correct (not Compilable as is because I don't know how to post the project), Example):

File FormRelease.dpr:

program FormRelease;

uses
  FastMM4 in 'FastMM4.pas',
  Vcl.Forms,
  MainForm_fm in 'MainForm_fm.pas' {MainForm},
  MyForm_fm in 'MyForm_fm.pas' {MyForm};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

File MainForm_fm.pas:

unit MainForm_fm;

interface

uses
  Vcl.Forms,
  MyForm_fm;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

  private
    FMyForm: TMyForm;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FMyForm := TMyForm.Create(nil);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FMyForm.Release;
end;

end.

File MyForm_fm.pas:

unit MyForm_fm;

interface

uses
  Vcl.Forms;

type
  TMyForm = class(TForm)
  end;

implementation

{$R *.dfm}

end.

Excerpt from file FormRelease_MemoryManager_EventLog.txt, after starting the program and stopping it with alt-F4:

This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):

13 - 20 bytes: TList x 1, Unknown x 1
21 - 36 bytes: TPen x 1, TMargins x 1, TPadding x 1, TIconImage x 1, TBrush x 2, TTouchManager x 1, TSizeConstraints x 1, UnicodeString x 1, Unknown x 3
37 - 52 bytes: TGlassFrame x 1, TFont x 2
53 - 68 bytes: TIcon x 1
69 - 84 bytes: TControlScrollBar x 2
101 - 116 bytes: TControlCanvas x 1
149 - 164 bytes: Unknown x 1
917 - 1012 bytes: TMyForm x 1

Why is there TMyForm x 1 left if the CM_RELEASE posted with FMyForm.Release has been handled?


Solution

  • It sounds like two separate things.

    First, you are explicitly creating your form, so you should explicitly free it. You shouldn't need to call Release except for special cases like a form freeing itself from within one of its own event handlers.

    MyForm: TForm;
    ...
    MyForm := TForm.Create(nil);
    try
      // Only reference MyForm in this block
    finally
      MyForm.Free;
    end;
    

    Second, calling Free isn't causing the problem. I think it's revealing it. If I had to guess, the access violation is happening because MyForm or one of its components is being referenced outside of this block. Use the debugger to find this reference and fix it.