HandyOrg/HandyControl · error · Win32Exception

Win32Exception

Error message

Win32Exception

What it means

InteropMethods.GetDC is the shared P/Invoke wrapper around user32 GetDC. It calls IntGetDC(hWnd) and throws Win32Exception (with Marshal.GetLastWin32Error() as the error code) whenever the native call returns a null HDC. A successful HDC is registered with HandleCollector under CommonHandles.HDC so it gets released later.

Solutions

  1. Check the exception's ErrorCode/NativeErrorCode to identify the exact Win32 failure (e.g. ERROR_NO_SYSTEM_RESOURCES, ERROR_INVALID_WINDOW_HANDLE).
  2. Fix GDI handle leaks in the application (pair every GetDC with ReleaseDC; watch the GDI Objects counter in Task Manager).
  3. Only call DC-dependent APIs from a thread in an interactive desktop session.
  4. Wrap calls in try-catch (Win32Exception) and degrade gracefully (assume 96 DPI or skip rendering work).
  5. If triggered by a destroyed window handle, ensure the HWND is still alive (IsWindow) before requesting its DC.

Example fix

// before
var dc = InteropMethods.GetDC(new HandleRef(null, hwnd)); // throws Win32Exception

// after
IntPtr dc;
try { dc = InteropMethods.GetDC(new HandleRef(null, hwnd)); }
catch (Win32Exception ex)
{
    Log.Warn($"GetDC failed: {ex.NativeErrorCode}");
    return; // degrade gracefully
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check GDI headroom and window validity where possible
bool canUse = Environment.UserInteractive; // and verify hwnd via IsWindow when not IntPtr.Zero

Type guard

static bool IsValidHwnd(IntPtr hwnd) => hwnd == IntPtr.Zero || InteropMethods.IsWindow(hwnd);

Try / catch

try { hdc = InteropMethods.GetDC(new HandleRef(null, hwnd)); }
catch (Win32Exception ex) { Log.Warn($"GetDC failed: {ex.NativeErrorCode}"); hdc = IntPtr.Zero; /* skip DC work */ }

Prevention

When it happens

Trigger: Any HandyControl code path that acquires a device context — e.g. WindowHelper DPI detection, window rendering helpers — when GetDC fails: invalid window HandleRef, GDI object quota exhausted, or no desktop available for the calling session.

Common situations: Session 0 / headless execution, disconnected terminal sessions, GDI handle leaks reaching the 10,000-per-process default quota, passing a destroyed window's handle.

Related errors


AI-assisted analysis of HandyOrg/HandyControl@2c0875ebd6 (2026-09-14). Data as JSON: /api/errors/3244191014e1ed27. Report an issue: GitHub.

Appendix: source

Thrown at src/Shared/HandyControl_Shared/Tools/Interop/InteropMethods.cs:114

    [DllImport(InteropValues.ExternDll.User32)]
    internal static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);

    [DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = "DestroyMenu", CharSet = CharSet.Auto)]
    [ResourceExposure(ResourceScope.None)]
    internal static extern bool IntDestroyMenu(HandleRef hMenu);

    [SecurityCritical]
    [SuppressUnmanagedCodeSecurity]
    [DllImport(InteropValues.ExternDll.User32, SetLastError = true, ExactSpelling = true, EntryPoint = nameof(GetDC),
        CharSet = CharSet.Auto)]
    internal static extern IntPtr IntGetDC(HandleRef hWnd);

    [SecurityCritical]
    internal static IntPtr GetDC(HandleRef hWnd)
    {
        var hDc = IntGetDC(hWnd);
        if (hDc == IntPtr.Zero) throw new Win32Exception();

        return HandleCollector.Add(hDc, CommonHandles.HDC);
    }

    [SecurityCritical]
    [SuppressUnmanagedCodeSecurity]
    [DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = nameof(ReleaseDC), CharSet = CharSet.Auto)]
    internal static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC);

    [SecurityCritical]
    internal static int ReleaseDC(HandleRef hWnd, HandleRef hDC)
    {
        HandleCollector.Remove((IntPtr) hDC, CommonHandles.HDC);
        return IntReleaseDC(hWnd, hDC);
    }

    [SecurityCritical]
    [SuppressUnmanagedCodeSecurity]

View on GitHub (pinned to 2c0875ebd6)