Flow-Launcher/Flow.Launcher · warning · Exception

Failed to delete HBitmap.

Error message

Failed to delete HBitmap.

What it means

Thrown by GetBitmapSourceFromHBitmap when gdi32!DeleteObject returns false for the HBITMAP returned by the Shell in MENUITEMINFO.hbmpItem/hbmpChecked. CreateBitmapSourceFromHBitmap has already copied the pixels into a WPF BitmapSource, so this throw is purely a resource-leak-prevention check. Note the surrounding catch swallows only COMException, so this Exception propagates up and aborts the whole menu build.

Source

Thrown at Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs:389

                menuItems.Add(new ContextMenuItem(prefix + menuText, icon, mii.wID));
            }
        }
    }

    private static BitmapSource GetBitmapSourceFromHBitmap(IntPtr hBitmap)
    {
        try
        {
            var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromWidthAndHeight(16, 16)
            );

            if (!DeleteObject(hBitmap))
            {
                throw new Exception("Failed to delete HBitmap.");
            }

            return bitmapSource;
        }
        catch (COMException)
        {
            // ignore
        }

        return null;
    }
}

#region Data Structures

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E6-0000-0000-C000-000000000046")]

View on GitHub (pinned to 7fc63b07bb)

Solutions

  1. Do not throw on DeleteObject failure: log a warning and continue. The pixels are already copied into the BitmapSource.
  2. Broaden the catch to also swallow this Exception (currently only COMException is swallowed), so the rest of the menu still renders.
  3. If the bitmap must be owned, copy it via CopyFromScreen or by locking the BitmapSource before attempting DeleteObject.
  4. Detect shared handles (GetObjectType == OBJ_BITMAP stock) and skip deletion.

Example fix

// before
if (!DeleteObject(hBitmap))
{
    throw new Exception("Failed to delete HBitmap.");
}

// after - do not let a non-owned handle abort the whole menu
if (!DeleteObject(hBitmap))
{
    Main.Context.API.LogWarn(ClassName, $"Unable to delete shell HBITMAP 0x{hBitmap.ToInt64():X}; it may be shared.");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// GDI ownership cannot be pre-checked; the safe guard is to not throw on failure.

Try / catch

// Broaden the catch in GetBitmapSourceFromHBitmap so a non-owned handle cannot abort the whole menu.
try { ... if (!DeleteObject(hBitmap)) Main.Context.API.LogWarn(ClassName, $"Unable to delete HBITMAP 0x{hBitmap.ToInt64():X}"); }
catch (Exception ex) { Main.Context.API.LogWarn(ClassName, $"Bitmap conversion failed: {ex.Message}"); }

Prevention

When it happens

Trigger: The HBITMAP is a shared/predefined handle that GDI refuses to delete (e.g. a stock object), the handle was already freed by another shell extension, the handle is actually an HICON or HBMP_FOREACH disguised, or DeleteObject is being called from a different desktop/session than the one that owns the object.

Common situations: A shell extension returns a cached system bitmap; an extension returns an HBITMAP wrapped via SHCreateShellFolderView that is not independently deletable; running under a different DPI awareness context so GDI handles belong to a different heap.

Related errors


AI-assisted analysis of Flow-Launcher/Flow.Launcher@7fc63b07bb (2026-08-13). Data as JSON: /api/errors/2cc1aba1d6a1db84. Report an issue: GitHub.