dotnet/wpf · error · Win32Exception
Win32Exception
Error message
Win32Exception
What it means
UnsafeNativeMethodsCLR.DeleteObject wraps the Win32 DeleteObject GDI API. When IntDeleteObject returns false, the last Win32 error is not consulted and a bare Win32Exception is thrown, meaning GDI could not destroy the object handle. This library throws it to surface GDI handle-destruction failures rather than silently leaking or ignoring them.
Solutions
- Ensure the GDI object is not still selected into an HDC (select the old object back in with SelectObject before DeleteObject).
- Verify each handle is deleted exactly once — pair every Create*/Get* handle with a single DeleteObject and guard with a deleted flag.
- Check that the handle was created on the same thread that deletes it, or marshal cleanup appropriately.
- Inspect GetLastWin32Error via a debugger or temporarily call IntDeleteObject directly to learn the underlying error code (e.g. ERROR_INVALID_HANDLE).
Example fix
// before
if (hBrush != IntPtr.Zero) { UnsafeNativeMethodsCLR.DeleteObject(new HandleRef(this, hBrush)); }
// after
if (hBrush != IntPtr.Zero && !_brushDeleted)
{
UnsafeNativeMethodsCLR.SelectObject(hDC, oldBrush); // deselect first
UnsafeNativeMethodsCLR.DeleteObject(new HandleRef(this, hBrush));
_brushDeleted = true;
} Defensive patterns
Strategy: validation
Validate before calling
if (handle == IntPtr.Zero || _deleted) throw new InvalidOperationException("GDI handle already released"); Type guard
bool IsValidGdiHandle(IntPtr h) => h != IntPtr.Zero;
Try / catch
try { UnsafeNativeMethodsCLR.DeleteObject(new HandleRef(this, h)); }
catch (Win32Exception ex) { Trace.WriteLine($"DeleteObject failed: {ex.NativeErrorCode}"); } Prevention
- Select objects out of an HDC before deleting them
- Track handle ownership so each GDI handle is deleted exactly once
- Never delete stock objects (GetStockObject results)
- Release handles on the thread that created them
When it happens
Trigger: Calling DeleteObject on an already-deleted GDI handle, on a handle owned by another thread, or on a stock object (e.g. stock brushes/pens/fetch from GetStockObject) that cannot be deleted; also invalid HandleRef values passed from GDIExporter cleanup.
Common situations: Double-free of GDI handles during window/bitmap cleanup, finalizer racing with explicit disposal, deleting fonts/brushes still selected into a device context (HDC), running under GDI handle-pressure conditions.
Related errors
- PrintSystemException.PrintServer.Generic
- Unable to combine two HRGNs.
- Unable to create a device context from the specified device…
- win32Error (System.ComponentModel.Win32Exception)
- Win32Exception(errorCode)
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/75902ce5eb7a909f.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsCLR.cs:101
[DllImport(ExternDll.Gdi32, EntryPoint = "GetStockObject", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CriticalGetStockObject(int stockObject);
[DllImport(ExternDll.User32, EntryPoint = "FillRect", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int CriticalFillRect(IntPtr hdc, ref NativeMethods.RECT rcFill, IntPtr brush);
[DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetBitmapBits(HandleRef hbmp, int cbBuffer, byte[] lpvBits);
[DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool ShowWindow(HandleRef hWnd, int nCmdShow);
public static void DeleteObject(HandleRef hObject)
{
HandleCollector.Remove((IntPtr)hObject, NativeMethods.CommonHandles.GDI);
if (!IntDeleteObject(hObject))
{
throw new Win32Exception();
}
}
[DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, EntryPoint = "DeleteObject", CharSet = CharSet.Auto)]
public static extern bool IntDeleteObject(HandleRef hObject);
[DllImport(ExternDll.Gdi32, EntryPoint = "SelectObject", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr CriticalSelectObject(HandleRef hdc, IntPtr obj);
[DllImport(ExternDll.User32, EntryPoint = "PrintWindow", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool CriticalPrintWindow(HandleRef hWnd, HandleRef hDC, int flags);
[DllImport(ExternDll.User32, EntryPoint = "RedrawWindow", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool CriticalRedrawWindow(HandleRef hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, int flags);
[DllImport(ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false)]
public static extern IntPtr ShellExecute(HandleRef hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
View on GitHub (pinned to 81131a70a4)