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
- Do not throw on DeleteObject failure: log a warning and continue. The pixels are already copied into the BitmapSource.
- Broaden the catch to also swallow this Exception (currently only COMException is swallowed), so the rest of the menu still renders.
- If the bitmap must be owned, copy it via CopyFromScreen or by locking the BitmapSource before attempting DeleteObject.
- 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
- Never throw from DeleteObject - the pixels are already copied.
- Log non-owned/shared handles and continue.
- Detect stock objects (GetObjectType) and skip deletion.
- Copy the bitmap into WPF before deleting, which the code already does.
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
- SHParseDisplayName failed
- SHBindToParent failed
- GetUIObjectOf failed
- InvokeCommand failed with code {hr:X}
- Failed to get the desktop shell folder
AI-assisted analysis of Flow-Launcher/Flow.Launcher@7fc63b07bb (2026-08-13).
Data as JSON: /api/errors/2cc1aba1d6a1db84.
Report an issue: GitHub.