What unnamed GDI resources could I be leaking

854 views Asked by At

I ran an overnight stress test of my application, and when i came in this morning the software had crashed from a Windows error.

The error occurred because the process was obviously out of GDI handles:

Process Explorer

enter image description here

Task Manager

enter image description here

The next thing is to figure out exactly which kind of GDI resource (e.g. Pen, Brush, Bitmap, Font, Region, DC) i'm leaking. For that i turned to NirSoft's GDIView:

enter image description here

  • Pen: 0
  • Ext Pen: 0
  • Brush: 4
  • Bitmap: 35
  • Font: 19
  • Palette: 1
  • Region: 3
  • DC: 11
  • Metafile DC: 0
  • Enhanced Metafile DC: 0
  • Other GDI: 0
  • GDI Total: 0
  • All GDI: 10,000

What could a GDI handle be, one that is not any known GDI type?

1

There are 1 answers

1
Ian Boyd On BEST ANSWER

The answer was GDI HFONT handles.

It is a Windows 8 issue that GDIView cannot show the font handles.

I used hooking to intercept every call to:

  • CreateFont
  • DestroyFont

and logged every handle creation, along with its stack trace of when it was allowed. At the end i created a report of all undeleted HFONTs.

How did i do it?

I used the Detours library for Delphi.

Step 1 - For every GDI function there is that creates something, we ask Detours to intercept the function.

  • We pass the address of our replacement function
  • and it returns the address of the original function (so we can call it)
var
    CreateFontIndirectAOriginal: function (const p1: TLogFontA): HFONT; stdcall = nil;
    DeleteObjectOriginal: function (p1: HGDIOBJ): BOOL; stdcall = nil;

CreateFontIndirectAOriginal := InterceptCreate(@CreateFontIndirectA, @CreateFontIndirectAIntercept);
DeleteObjectOriginal := InterceptCreate(@DeleteObject, @DeleteObjectIntercept);

Step 2 - Declare our versions of the GDI functions:

function CreateFontIndirectAIntercept(const p1: TLogFontA): HFONT; stdcall;
begin
    Result := CreateFontIndirectAOriginal(p1);
end;

function DeleteObjectIntercept(p1: HGDIOBJ): BOOL; stdcall;
begin
    Result := DeleteObjectOriginal(p1);
end;

Step 3 - Add code to track every font created by CreateFont, and every font destruction by DestroyObject

function CreateFontIndirectAIntercept(const p1: TLogFontA): HFONT; stdcall;
begin
    Result := TrampolineCreateFontIndirectA(p1);
    GdiLeakTrackerSvc.AddFont(Result);
end;

function DeleteObjectIntercept(p1: HGDIOBJ): BOOL; stdcall;
var
    objType: DWORD;
begin
    objType := GetObjectType(p1);
    Result := TrampolineDeleteObject(p1);

    case objType of
    OBJ_FONT:  GdiLeakTrackerSvc.RemoveObject(p1);
    end;
end;

And then the GdiLeakTrackerSvc service tracks all font creations, font destructions, and can let us know during program shutdown if anything leaked.