From c989818851900d35aa91821d26b520fad20cb68b Mon Sep 17 00:00:00 2001 From: Tom Edwards <109803929+TomEdwardsEnscape@users.noreply.github.com> Date: Mon, 13 Oct 2025 14:51:49 +0200 Subject: [PATCH] Add finalizer to Win32Icon which frees its GDI handle (#19813) Improve error messages when we run out of GDI handles Removed SetLastError from GDI methods; they don't use that system --- .../Interop/UnmanagedMethods.cs | 8 ++--- .../Avalonia.Win32/Interop/Win32Icon.cs | 35 +++++++++++++++---- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 6bf11cad9a..50a9c62136 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1370,10 +1370,10 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr CreateIconIndirect([In] ref ICONINFO iconInfo); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr CreateIconFromResourceEx(byte* pbIconBits, uint cbIconBits, int fIcon, int dwVersion, int csDesired, int cyDesired, int flags); @@ -1683,9 +1683,9 @@ namespace Avalonia.Win32.Interop [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); - [DllImport("gdi32.dll", SetLastError = true)] + [DllImport("gdi32.dll")] public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw); - [DllImport("gdi32.dll", SetLastError = true)] + [DllImport("gdi32.dll")] public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data); [DllImport("gdi32.dll")] public static extern int DeleteObject(IntPtr hObject); diff --git a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs index 0a23ffde4d..57da43da92 100644 --- a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs +++ b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs @@ -49,23 +49,30 @@ internal class Win32Icon : IDisposable } } + ~Win32Icon() + { + UnmanagedMethods.DestroyIcon(Handle); + } + public IntPtr Handle { get; private set; } public PixelSize Size { get; } private readonly byte[]? _bytes; + + private const string GdiError = "A GDI+ error occurred."; IntPtr CreateIcon(Bitmap bitmap, PixelPoint hotSpot = default) { var mainBitmap = CreateHBitmap(bitmap); if (mainBitmap == IntPtr.Zero) - throw new Win32Exception(); + throw new Win32Exception(GdiError); var alphaBitmap = AlphaToMask(bitmap); try { if (alphaBitmap == IntPtr.Zero) - throw new Win32Exception(); + throw new Win32Exception(GdiError); var info = new UnmanagedMethods.ICONINFO { IsIcon = false, @@ -77,7 +84,7 @@ internal class Win32Icon : IDisposable var hIcon = UnmanagedMethods.CreateIconIndirect(ref info); if (hIcon == IntPtr.Zero) - throw new Win32Exception(); + throw new Win32Exception(Marshal.GetLastWin32Error()); return hIcon; } finally @@ -303,6 +310,7 @@ internal class Win32Icon : IDisposable var bestSize = new PixelSize(bestWidth, bestHeight); + IntPtr handle; // Copy the bytes into an aligned buffer if needed. if ((_bestImageOffset % IntPtr.Size) != 0) { @@ -314,8 +322,8 @@ internal class Win32Icon : IDisposable { fixed (byte* pbAlignedBuffer = alignedBuffer) { - return (UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1, - 0x00030000, 0, 0, 0), bestSize); + handle = UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1, + 0x00030000, 0, 0, 0); } } finally @@ -328,14 +336,26 @@ internal class Win32Icon : IDisposable { try { - return (UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes, - 1, 0x00030000, 0, 0, 0), bestSize); + handle = UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes, + 1, 0x00030000, 0, 0, 0); } catch (OverflowException) { return default; } } + + if (handle == default) + { + var error = Marshal.GetLastWin32Error(); + // If there we are out of GDI handles then our fallback won't work either, so throw now. + if (error == (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_NOT_ENOUGH_MEMORY) + { + throw new Win32Exception(error); + } + } + + return (handle, bestSize); } } @@ -348,5 +368,6 @@ internal class Win32Icon : IDisposable { UnmanagedMethods.DestroyIcon(Handle); Handle = IntPtr.Zero; + GC.SuppressFinalize(this); } }