How to detect GDI handle leaks

By Stephen Kellett
4 September, 2018

Working with GDI in Windows, whether you’re using Win32 calls or MFC, you’re concerned with pens, brushes, fonts, bitmaps and regions for drawing. You may also be concerned with Palettes, although with our full-colour displays these days working with palettes is something of a rarity.

The typical way to work with a GDI object, for example, a pen, is shown below. Create the pen, select it into the DC, do the drawing, select the original object back into the DC, and delete the pen.

void CtestGDISelectObjectDlg::drawSomethingWithARedPen(HDC hDC)
{
	HPEN		hPen;
	HPEN		hPenOld;
	HGDIOBJ		retVal;

	hPen = ::CreatePen(PS_SOLID, 0, RGB(255, 0, 0));

	hPenOld = (HPEN)SelectObject(hDC, hPen);

	doDrawing1(hDC);

	retVal = SelectObject(hDC, hPenOld);

	DeleteObject(hPen);
}

So far so good. But there is more than one way to leak a GDI resource handle.

Leaking GDI objects, type 1

The most common way people leak GDI objects is because they simply forget to delete them. Here’s the previous example, modified to leak the pen. (Don’t do this!).

void CtestGDISelectObjectDlg::drawSomethingWithARedPen(HDC hDC)
{
	HPEN		hPen;
	HPEN		hPenOld;
	HGDIOBJ		retVal;

	hPen = ::CreatePen(PS_SOLID, 0, RGB(255, 0, 0));

	hPenOld = (HPEN)SelectObject(hDC, hPen);

	doDrawing1(hDC);

	retVal = SelectObject(hDC, hPenOld);
}

The author of the code forgot to delete the pen using DeleteObject(hPen). This leak looks like this in Memory Validator.

Memory Validator showing a leaked GDI pen

Leaking GDI objects, type 2

There is another way to leak GDI objects, even when you think you’ve deleted all the objects you created. Take a look at this code. Does it leak?

	HPEN		hPen1;
	HPEN		hPen2;
	HPEN		hPenOld;
	HGDIOBJ		retVal;

	hPen1 = ::CreatePen(PS_SOLID, 0, RGB(255, 0, 0));
	hPen2 = ::CreatePen(PS_DASHDOTDOT, 0, RGB(255, 0, 0));

	hPenOld = (HPEN)SelectObject(hDC, hPen1);

	doDrawing1(hDC);

	retVal = SelectObject(hDC, hPen2);
	
	doDrawing2(hDC);

	DeleteObject(hPen1);
	DeleteObject(hPen2);

A quick examination shows that two pens are created, some work is done with each pen, then the pens are deleted.

No leaks, right?

Wrong! hPen2 was selected into the DC for use with doDrawing2() but was not deselected from the DC prior to DeleteObject(hPen2);. This means that the call to DeleteObject() will fail as the pen is still in use. hPen2 has been leaked.

Memory Validator can detect this (as of V7.38, released today). Here’s what that looks like:

Memory Validator showing a leaked GDI pen because the pen is still in use

Expanding the source and you can easily see the failed DeleteObject() call and the SelectObject() call that mean the object was still in use.

Memory Validator showing a leaked GDI pen because the pen is still in use - with source code

Here’s what the non-leaking code should look like:

	HPEN		hPen1;
	HPEN		hPen2;
	HPEN		hPenOld;
	HGDIOBJ		retVal;

	hPen1 = ::CreatePen(PS_SOLID, 0, RGB(255, 0, 0));
	hPen2 = ::CreatePen(PS_DASHDOTDOT, 0, RGB(255, 0, 0));

	hPenOld = (HPEN)SelectObject(hDC, hPen1);

	doDrawing1(hDC);

	retVal = SelectObject(hDC, hPen2);
	
	doDrawing2(hDC);

	SelectObject(hDC, hPenOld);

	DeleteObject(hPen1);
	DeleteObject(hPen2);

To ensure your object is not still selected into the DC you can select the value that was returned to you by the first call (hPenOld in the code above), or you can select a stock object into the DC instead. For example:

	SelectObject(hDC, GetStockObject(BLACK_PEN));

Conclusion

When working with GDI objects you need to keep track of two things:

  1. Creation and Deletion of GDI objects. For every pen, brush, font, bitmap, and palette that you create you must delete those objects when you are finished with them. Delete objects using DeleteObject().
  2. You need to ensure that none of the objects that you create is selected into a DC when you try to delete them.

Can your memory leak tool detect both types of GDI handle leak?

Memory Validator can detect both types of GDI handle leak.

Fully functional, free for 30 days