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

  1. Ensure the GDI object is not still selected into an HDC (select the old object back in with SelectObject before DeleteObject).
  2. Verify each handle is deleted exactly once — pair every Create*/Get* handle with a single DeleteObject and guard with a deleted flag.
  3. Check that the handle was created on the same thread that deletes it, or marshal cleanup appropriately.
  4. 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

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


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)