1
0
Эх сурвалжийг харах

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
Tom Edwards 6 өдөр өмнө
parent
commit
f5446254e8

+ 4 - 4
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);

+ 28 - 7
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);
     }
 }