Browse Source

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
pull/19864/head
Tom Edwards 4 months ago
committed by GitHub
parent
commit
f5446254e8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  2. 35
      src/Windows/Avalonia.Win32/Interop/Win32Icon.cs

8
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1370,10 +1370,10 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)] [DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)]
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); 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); 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, public static extern IntPtr CreateIconFromResourceEx(byte* pbIconBits, uint cbIconBits,
int fIcon, int dwVersion, int csDesired, int cyDesired, int flags); int fIcon, int dwVersion, int csDesired, int cyDesired, int flags);
@ -1683,9 +1683,9 @@ namespace Avalonia.Win32.Interop
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject); 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); 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); public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern int DeleteObject(IntPtr hObject); public static extern int DeleteObject(IntPtr hObject);

35
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 IntPtr Handle { get; private set; }
public PixelSize Size { get; } public PixelSize Size { get; }
private readonly byte[]? _bytes; private readonly byte[]? _bytes;
private const string GdiError = "A GDI+ error occurred.";
IntPtr CreateIcon(Bitmap bitmap, PixelPoint hotSpot = default) IntPtr CreateIcon(Bitmap bitmap, PixelPoint hotSpot = default)
{ {
var mainBitmap = CreateHBitmap(bitmap); var mainBitmap = CreateHBitmap(bitmap);
if (mainBitmap == IntPtr.Zero) if (mainBitmap == IntPtr.Zero)
throw new Win32Exception(); throw new Win32Exception(GdiError);
var alphaBitmap = AlphaToMask(bitmap); var alphaBitmap = AlphaToMask(bitmap);
try try
{ {
if (alphaBitmap == IntPtr.Zero) if (alphaBitmap == IntPtr.Zero)
throw new Win32Exception(); throw new Win32Exception(GdiError);
var info = new UnmanagedMethods.ICONINFO var info = new UnmanagedMethods.ICONINFO
{ {
IsIcon = false, IsIcon = false,
@ -77,7 +84,7 @@ internal class Win32Icon : IDisposable
var hIcon = UnmanagedMethods.CreateIconIndirect(ref info); var hIcon = UnmanagedMethods.CreateIconIndirect(ref info);
if (hIcon == IntPtr.Zero) if (hIcon == IntPtr.Zero)
throw new Win32Exception(); throw new Win32Exception(Marshal.GetLastWin32Error());
return hIcon; return hIcon;
} }
finally finally
@ -303,6 +310,7 @@ internal class Win32Icon : IDisposable
var bestSize = new PixelSize(bestWidth, bestHeight); var bestSize = new PixelSize(bestWidth, bestHeight);
IntPtr handle;
// Copy the bytes into an aligned buffer if needed. // Copy the bytes into an aligned buffer if needed.
if ((_bestImageOffset % IntPtr.Size) != 0) if ((_bestImageOffset % IntPtr.Size) != 0)
{ {
@ -314,8 +322,8 @@ internal class Win32Icon : IDisposable
{ {
fixed (byte* pbAlignedBuffer = alignedBuffer) fixed (byte* pbAlignedBuffer = alignedBuffer)
{ {
return (UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1, handle = UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1,
0x00030000, 0, 0, 0), bestSize); 0x00030000, 0, 0, 0);
} }
} }
finally finally
@ -328,14 +336,26 @@ internal class Win32Icon : IDisposable
{ {
try try
{ {
return (UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes, handle = UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes,
1, 0x00030000, 0, 0, 0), bestSize); 1, 0x00030000, 0, 0, 0);
} }
catch (OverflowException) catch (OverflowException)
{ {
return default; 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); UnmanagedMethods.DestroyIcon(Handle);
Handle = IntPtr.Zero; Handle = IntPtr.Zero;
GC.SuppressFinalize(this);
} }
} }

Loading…
Cancel
Save