Python managed Win32 GDI resources

A quick note before we begin, whilst this article specifically describes a way of writing cleaner and safer Win32 GDI code in Python, these same techniques could be applied to any corresponding API which requires manual resource management.

A task came up recently which required utilising some old Win32 GDI APIs for a legacy application maintenance.  There are various examples online of using the ctypes library in Python for getting access to the Win32 APIs, however the code tends to be largely a straight port of C-style code without utilising any pythonic niceties.

A typical construct seen throughout GDI code is the create, use, destroy cycle, e.g:

from ctypes import windll
hDC = windll.gdi32.CreateCompatibleDC(0) # Create
# Use Device Context here
windll.gdi32.DeleteDC # Destroy

One issue with this is that different GDI constructs use different clean-up calls.  For example, the handle returned by GetDC must be disposed with ReleaseDC, whereas the handle created by CreateBitmap should be disposed with DeleteObject.  Making sure the correct clean-up method is called for each resource type, especially as the complexity of the code increases can become an issue.

Since the functions are opaque calls into DLLs, there is no code hinting available when writing the code, which requires either an encyclopaedic knowledge of the Win32 API, or the GDI documentation permanently open on a second screen.

Additionally, there are calls like SelectObject, which is used to bind a GDI resource with a Device Context, that can create subtle potential for GDI leaks if not handled correctly.

For instance, you’ll often find code examples like this online:

hPen = windll.gdi32.CreatePen(PS_SOLID, 3, 0xFF0000)
windll.gdi32.SelectObject(hDC, hPen)
# Use pen here
windll.gdi32.DeleteObject(hPen) # Leak! Still selected into the DC, pen isn’t deleted!

The correct way to handle DC object selection is to retain the handle returned by the initial SelectObject call (which will typically be a GDI stock object on a fresh DC), then once the GDI object is no longer required, re-select that handle back into the DC before deleting it.

hPen = windll.gdi32.CreatePen(PS_SOLID, 3, 0xFF0000)
hOldPen = windll.gdi32.SelectObject(hDC, hPen)
# Use pen here
windll.gdi32.SelectObject(hDC, hOldPen) # De-select
windll.gdi32.DeleteObject(hPen) # Then delete

All this clean-up code can get quite verbose, which makes it more difficult to reason about what the code is doing in amongst all the resource management and obscure GDI hoops that must be jumped through.

Finally, error handling can become tricky with this sort of code and a source of GDI leaks, since unless the GDI calls are unwound in the correct order then there is potential for those resources to be left hanging around if an exception occurs.

So whilst waiting for inspiration to strike and getting on with writing another part of the application that dealt with reading files, I was writing some standard file access code e.g.:

with open("file.xyz", "r") as f:
	fileData = f.read()

It occurred to me that although I’d used the with statement to ensure that the file is properly closed in case of error, that I’d never really investigate how it worked.  It turns out that this uses something called a statement context manager, which handles entry into and exit from a runtime context.

A quick check in the documentation reveals that implementing our own context manager is straightforward, a rudimentary example of this would be:

class MyScope(object):
	def __enter__(self):
		print("Entering scope!")
	
	def __exit__(self, type, value, tb):
		print("Exiting scope!")

with MyScope():
	print("In scope")

This prints:

Entering scope!
In scope
Exiting scope!

Nice, so now we can execute code when entering and existing from a scope block.  What’s really nice though, is that the exit code will still be called even in the case an error is raised, similar to a try / finally block:

with MyScope():
	raise Exception("nope")

This outputs:

Entering scope!
Exiting scope!
Exception: nope

As we can see, the exit block gets called before the exception is bubbled up to the caller.

So, with that little aside into context mangers out of the way, is there a way that these two concepts can be tied back together, and the narrative of this article brought back on track?  Thankfully, yes.

So back in GDI world, the familiar create, use, destroy cycle seems to map well to the enter, use, exit functionality of the context manager, so let’s try merging them together.  A new utility class can be built for each pair of calls, that can take the input parameters in the constructor, calls the object creation method in enter, and the corresponding destruction method in exit:

class CreateCompatibleDC(object):
	"""Context manager for GDI32 CreateCompatibleDC function"""
	def __init__(self, hRefDC=0):
		self.hRefDC = hRefDC
	
	def __enter__(self):
		print("Create compatible DC")
		self.hDC = windll.gdi32.CreateCompatibleDC(self.hRefDC)
		return self.hDC
	
	def __exit__(self, type, value, tb):
		print("Delete DC")
		windll.gdi32.DeleteDC(self.hDC)

The class can now be utilised in the calling code like this:

with CreateCompatibleDC(0) as hDC:
	print("Use DC: %d" % hDC)

which outputs:

Create compatible DC
Use DC: -1157555815
Delete DC

Great, it looks like the calls are all being made in the right places, the code is much simpler without all the boilerplate GDI stuff getting in the way.  The Python class also offers code completion in the IDE on the parameter(s) required and it even properly cleans up on exceptions!

Of course, in any production code the print messages wouldn’t be used, they’re just there to show that the code is doing the right things at the right times.

This same technique can the be used for other calls too, for example:

class CreatePen(object):
	"""Context manager for GDI32 CreatePen function"""
	def __init__(self, style, width, colour):
		self.style = style
		self.width = width
		self.colour = colour
	
	def __enter__(self):
		print("Create pen")
		self.hPen = windll.gdi32.CreatePen(self.style, self.width, self.colour)
		return self.hPen
	
	def __exit__(self, type, value, tb):
		print("Delete pen")
		windll.gdi32.DeleteObject(self.hPen)

class SelectObject(object):
	"""Context manager for GDI32 SelectObject function"""
	def __init__(self, hDC, hObj):
		self.hDC = hDC
		self.hObj = hObj
	
	def __enter__(self):
		print("Select object")
		self.hOldObj = windll.gdi32.SelectObject(self.hDC, self.hObj)
		return self.hOldObj
	
	def __exit__(self, type, value, tb):
		print("Deselect object")
		windll.gdi32.SelectObject(self.hDC, self.hOldObj)

Now the code from the beginning of the article can be written more simply as:

with CreateCompatibleDC() as hDC:
	with CreatePen(PS_SOLID, 3, 0xFF0000) as hPen:
		with SelectObject(hDC, hPen):
			print("Use Pen: %d in DC: %d" % (hPen, hDC))

Note here that we’re now utilising the default value specified in the create compatible DC class constructor, and that the returned handle from the select object call is ignored since it’s only useful internally for clean-up.

This outputs:

Create compatible DC
Create pen
Select object
Use Pen: -399500450 in DC: 553719962
Deselect object
Delete pen
Delete DC

Since this is starting to resemble a pyramid of doom, all those with statements can get collapsed into one using backslash line continuation characters, and are still executed in order even in the case of error:

with CreateCompatibleDC() as hDC, \
	CreatePen(PS_SOLID, 3, 0xFF0000) as hPen, \
	SelectObject(hDC, hPen):
	raise Exception("testing")

This prints:

Create compatible DC
Create pen
Select object
Deselect object
Delete pen
Delete DC
Exception: testing

So as we can see, the clean-up code gets executed in order, so even if errors occur there is a guarantee that no GDI objects will get leaked.

Hopefully this technique can be utilised in your own code to help manage resources, clean up code and prevent unmanaged resource leaks.

Leave a comment