Windows Forms – System.OutOfMemoryException: Screen-compatible bitmap cannot be created. The screen bitmap format cannot be determined.

Description

Occasionally, Windows Forms application user will get an error message as shown in below snapshot.
screen bitmap error

Analysis

If you have done win32 development, you will immediately suspect GDI handles. I would like to point out the steps which you can take to troubleshoot and confirm if there is a GDI Leak in a process.

When a user reports this issue which is not reproducible at will, in that case you can ask user to launch task manager and go to view->select columns and check Handles and GDI Handles. Even better, if you can use process explorer.

Things to remember

1. 32 bit Windows OS(XP and above) uses DWORD which is 4 bytes for either user handle or gdi handle. Most significant 16 bits are used for GDI handles so 2^16 = 65536 is a theoretical limit on number of GDI or USER handles a Win32 Process can have.

2. Default value of number of handles configured in a 32 bit operating system are 10k which is configurable in the registry

3. “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota” has the default value of GDI handles

4. “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota” has the default value of User Handles

5. GDI handles are not just bitmaps, but it can be any device context object for example, Font, Pen, DC etc

I have not dissected GDI Handle yet so I can’t get into too much detail on GDI data structure on Windows XP. However, another point to remember is GDI kernel data structure is stored in a system paged pool which is a physical memory resident so what that means is although the theoretical limit on number of GDI handles is almost 65k per process but that doesn’t mean your process can have 65k GDI handles so before changing the registry you need to think twice and then twice again, does your application really need more than 10k GDI Handles?


Below is the number of GDI Handles as shown in a process explorer’s snapshot taken at customer site when this error occurred

screen bitmap error

As usual, we need to collect the hang dump at the time of this exception


1. I really don’t know how to get the list of gdi handles from a memory dump, there used to be a gdi windbg extension dll from microsoft but that has been discontinued with windows xp.


2. if you look at process explorer snapshot as shown above, you will notice that the number of GDI Handles are 9999 and after that it fails to create the compatible bitmap.


3. Let’s open the hang dump and you can still run !analyze -v command to get the exception record


EXCEPTION_OBJECT: !pe 1a2b14ec
Exception object: 1a2b14ec
Exception type: System.OutOfMemoryException
Message: Screen-compatible bitmap cannot be created. The screen bitmap format cannot be determined.
MANAGED_STACK:
SP               IP                                      Function
0012E46C  7AE32C56
System_Drawing_ni!System.Drawing.BufferedGraphicsContext.bFillBitmapInfo(IntPtr, IntPtr, BITMAPINFO_FLAT ByRef)
…………………………………..
System_Drawing_ni!System.Drawing.BufferedGraphicsContext.Allocate(IntPtr, System.Drawing.Rectangle)
0:000> !ip2md 7AE32C56
MethodDesc: 7adff018
Method Name: System.Drawing.BufferedGraphicsContext.bFillBitmapInfo(IntPtr, IntPtr, BITMAPINFO_FLAT ByRef)
0:000> !dumpil 7adff018
ilAddr = 7aeb0f64
IL_0000: ldsfld System.IntPtr::Zero
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: stloc.1
.try
{
IL_0008: ldnull
IL_0009: ldarg.1
IL_000a: newobj System.Runtime.InteropServices.HandleRef::.ctor
IL_000f: ldc.i4.1
IL_0010: ldc.i4.1
IL_0011: call System.Drawing.SafeNativeMethods::CreateCompatibleBitmap
IL_0016: stloc.0
IL_0017: ldloc.0
IL_0018: ldsfld System.IntPtr::Zero
IL_001d: call System.IntPtr::op_Equality
IL_0022: brfalse.s IL_0034
IL_0024: ldstr “GraphicsBufferQueryFail”
IL_0029: call System.Drawing.SR::GetString
IL_002e: newobj System.OutOfMemoryException::.ctor

IL_0033: throw


Dump the IL for the corresponding IP, you will notice that it throws System.OutOfMemoryException when Native Method call ( GDI32.dll!CreateCompatibleBitmap) returns IntPtr.Zero.


4. This confirms that we have an issue here with GDI Handles, since this is a managed application so we can find the System.Drawing, Font, Pen and other GDI objects surviving the Garbage Collection.


5. You can just dump GDI objects on managed heap and find out where the objects are rooted at


6.  In this particular case, the culprit was System.Windows.Forms.Control+FontHandleWrapper

Below is the finalizequeue stats

0:000> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
———————————-
generation 0 has 34 finalizable objects (19826184->1982620c)
generation 1 has 6 finalizable objects (1982616c->19826184)
generation 2 has 84919 finalizable objects (197d3290->1982616c)
Ready for finalization 0 objects (1982620c->1982620c)

………………………………………………………………………………………………………………….

7b225228     9811       156976 System.Windows.Forms.Control+FontHandleWrapper
7b21e930     9816      1649088 System.Windows.Forms.TextBox
……………………………………………………………………………………………………………………
Total 84959 objects

and the call stack for HFONT gdi handle leak is belowBelow is the call stack to set a new Font object for every TextBox Control

System.Windows.Forms.Control+FontHandleWrapper..ctor(System.Drawing.Font)
System.Windows.Forms.Control.get_FontHandle()
System.Windows.Forms.Control.get_FontHandle()
System.Windows.Forms.Control.SetWindowFont()
System.Windows.Forms.Control.OnHandleCreated
This Windows Forms application maintains a collection of controls and a new font gets created everytime but never gets disposed along with the controls

And the root cause for Control not getting disposed is

022d28f8(System.Windows.Forms.ToolStrip)->
022d2644(MyApp.Controls.UserControl)->
022d6ed4(MyApp.MyPanel)->
022d84d0(System.Windows.Forms.ContextMenuStrip)->
021546f4(System.Windows.Forms.TextBox)->
02154608(System.Windows.Forms.PropertyStore)->

a. Form contains ContextMenuStrip member

b. Each of the created Control has a reference to ContextMenuStrip(Control.ContextMenuStrip = Form.ContextMenuStrip)

c. Even after a Control is removed, it still holds reference to ContextMenuStrip which is a member of Main Form

d. Unless Main Form is disposed, Controls will survive GC although Dispose is called on each of the Control on OnControlRemoved Event

e. This issue can be easily resolved by setting Control.ContextMenuStrip = null when a control is removed