7
votes

Embarcadero's TTaskbar has a memory leak. Since I dropped this control on my form, FastMM reports a leak every time I close the app.

I tried to mute FastMM with this code:

procedure TMainForm.FormCreate(Sender: TObject);
begin
 fastmm4.RegisterExpectedMemoryLeak(Taskbar);
end;

but it won't work. How to register this leak?


A memory block has been leaked. The size is: 100

This block was allocated by thread 0xC64, and the stack trace (return addresses) at the time was: 406A52 409A7B 409CAC 4283A0

[System.SysUtils][System][System.SysUtils.FmtStr] 409CC6 40D775 7628A65F
[Unknown function at StretchDIBits] 7731594E
[Unknown function at RtlpNtMakeTemporaryKey] 7731594E
[Unknown function at RtlpNtMakeTemporaryKey] 773168F8
[Unknown function at RtlpNtMakeTemporaryKey] 773168DC
[Unknown function at RtlpNtMakeTemporaryKey]

The block is currently used for an object of class: UnicodeString
The allocation number is: 2209

A memory block has been leaked. The size is: 36

This block was allocated by thread 0xC64, and the stack trace (return addresses) at the time was: 406A52 407D43 40846A 42CD40
[System.SysUtils][System][System.SysUtils.Exception.CreateFmt] 5DEDD7
[System.Win.TaskbarCore][System.Win][System.Win.TaskbarCore.TTaskbarBase.UpdateTab] 610F00
[Vcl.Taskbar][Vcl][Vcl.Taskbar.CheckMDI] 5DF39F
[System.Win.TaskbarCore][System.Win][System.Win.TaskbarCore.TTaskbarBase.ApplyTabsChanges] 610DB8
[Vcl.Taskbar][Vcl][Vcl.Taskbar.TCustomTaskbar.Initialize] 5EB044
[Vcl.Forms][Vcl][Vcl.Forms.TApplication.Run] 62573A
[MinimalTemplate.dpr][MinimalTemplate][MinimalTemplate.MinimalTemplate][26]

The block is currently used for an object of class: ETaskbarException
The allocation number is: 2207

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

21 - 36 bytes: ETaskbarException x 1
85 - 100 bytes: UnicodeString x 1
[Vcl.Forms][Vcl][Vcl.Forms.TCustomForm.SetVisible] 5F5010

1
I cannot reproduce here. OK now I can see where the leak is. It's in TTaskbarBase.UpdateTab. It's a shocker too!David Heffernan
I don't think it's going to be easy to deal with this. Fixing the problem is probably the way forward. I cannot do any more without a repro though. You do need to submit a bug report to emba though. Creating an exception but then failing to raise it?!!!David Heffernan
Repro project here: filedropper.com/repro_1Z80
@Altar: Dropping a TTaskbar component on a form and calling Taskbar1.UpdateTab() is sufficient for reproducing thisGünther the Beautiful
@GünthertheBeautiful-In my case is enough only to drop the component on the form!Z80

1 Answers

10
votes

The memory is leaked in this code from System.Win.TaskbarCore:

procedure TTaskbarBase.UpdateTab;
var
  LpfIsiconic: LONGBOOL;
  LHandle: HWND;
  LFlags: Integer;
begin
  if FTaskbarIsAvailable then
  begin
    LHandle := GetFormHandle;
    if not FRegistered and TaskBar.RegisterTab(LHandle) then
    begin
      TaskBar.SetTabOrder(LHandle);
      TaskBar.SetTabActive(LHandle);
      FRegistered := True;
    end
    else
      ETaskbarException.CreateFmt(SCouldNotRegisterTabException, [TaskBar.LastError]);
....

The final line creates an exception, and then does nothing with it. The exception and the string that it owns are leaked. As reported by FastMM.

You can register these objects as being leaked if you can obtain their addresses. However, you cannot do that. There is no way to refer to this exception object.

If you simply must avoid this mis-reported leak, and it makes sense that you would, then you'll need to include a fixed version of System.Win.TaskbarCore in your project. Make a copy of that file, and add it to your project. Then modify the code to fix the fault. My guess is that it would go like this:

if not FRegistered then
begin
  if TaskBar.RegisterTab(LHandle) then
  begin
    TaskBar.SetTabOrder(LHandle);
    TaskBar.SetTabActive(LHandle);
    FRegistered := True;
  end
  else
    raise ETaskbarException.CreateFmt(SCouldNotRegisterTabException, [TaskBar.LastError]);
end;   

Clearly this needs to be reported to Embarcadero. I suggest that you submit a bug report.


Another way around this is to try to avoid the bogus line executing at all. I believe that if you remove this line from your .dfm file, you should avoid the bogus line, and therefore avoid the leak:

Visible = True

Simply remove that line, it seems to be the trigger.

Note that I worked this out by cutting the project down to its bare bones. In order to reproduce the problem this is the minimal dfm file needed:

object Form1: TMainForm
  Visible = True
  object Taskbar1: TTaskbar
  end
end

And with this dfm file there is not leak:

object Form1: TMainForm
  object Taskbar1: TTaskbar
  end
end

By cutting the project down to the bare minimum, I was able to find the trigger. I cannot stress enough how valuable this technique of minimising a reproduction is.


Thanks to Remy for finding the QC report for this fault: QC#128865