Утечка памяти в WPF CreateBitmapSourceFromHBitmap()
Мне нужно нарисовать изображение попиксельно и отобразить его внутри WPF. Я пытаюсь сделать это с помощью System.Drawing.Bitmap
затем с помощью CreateBitmapSourceFromHBitmap()
создать BitmapSource
для управления изображением WPF. У меня где-то утечка памяти, потому что, когда CreateBitmapSourceFromBitmap()
вызывается многократно, использование памяти возрастает и не уменьшается до тех пор, пока приложение не будет завершено. Если я не позвоню CreateBitmapSourceFromBitmap()
нет заметного изменения в использовании памяти.
for (int i = 0; i < 100; i++)
{
var bmp = new System.Drawing.Bitmap(1000, 1000);
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
source = null;
bmp.Dispose();
bmp = null;
}
Что я могу сделать, чтобы освободить BitmapSource
объем памяти?
5 ответов
MSDN дляBitmap.GetHbitmap()
состояния:
замечания
Вы несете ответственность за вызов метода GDI DeleteObject для освобождения памяти, используемой растровым объектом GDI.
Так что используйте следующий код:
// at class level
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
// your code
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1000, 1000))
{
IntPtr hBitmap = bmp.GetHbitmap();
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
Я также заменил ваш Dispose()
позвонить по using
заявление.
Всякий раз, когда имеешь дело с неуправляемыми дескрипторами, может быть хорошей идеей использовать оболочки "безопасной ручки"
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return GdiNative.DeleteObject(handle) > 0;
}
}
Создайте один из них так, как только вы обнаружите дескриптор (в идеале ваши API никогда не будут выставлять IntPtr, они всегда будут возвращать безопасные дескрипторы):
IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);
И используйте это так:
using (handle)
{
... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
База SafeHandle дает вам автоматический шаблон одноразового использования / финализатора, все, что вам нужно сделать, это переопределить метод ReleaseHandle.
У меня было такое же требование и проблема (утечка памяти). Я реализовал то же решение, что помечено как ответ. Но хотя решение работает, оно вызвало недопустимый удар по производительности. Работая на i7, мое тестовое приложение показало стабильный 30–40% ЦП, увеличение оперативной памяти на 200–400 МБ, а сборщик мусора работал почти каждую миллисекунду.
Поскольку я занимаюсь обработкой видео, мне нужна гораздо лучшая производительность. Я придумал следующее, поэтому думал, что поделюсь.
Многоразовые глобальные объекты
//set up your Bitmap and WritableBitmap as you see fit
Bitmap colorBitmap = new Bitmap(..);
WriteableBitmap colorWB = new WriteableBitmap(..);
//choose appropriate bytes as per your pixel format, I'll cheat here an just pick 4
int bytesPerPixel = 4;
//rectangles will be used to identify what bits change
Rectangle colorBitmapRectangle = new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height);
Int32Rect colorBitmapInt32Rect = new Int32Rect(0, 0, colorWB.PixelWidth, colorWB.PixelHeight);
Код конверсии
private void ConvertBitmapToWritableBitmap()
{
BitmapData data = colorBitmap.LockBits(colorBitmapRectangle, ImageLockMode.WriteOnly, colorBitmap.PixelFormat);
colorWB.WritePixels(colorBitmapInt32Rect, data.Scan0, data.Width * data.Height * bytesPerPixel, data.Stride);
colorBitmap.UnlockBits(data);
}
Пример реализации
//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;
Результатом является стабильный 10–13% ЦП, 70–150 МБ ОЗУ, а сборщик мусора запускается только дважды за 6 минут работы.
В моем случае он не работал напрямую с этим методом. Пришлось дополнительно добавить чистый сборщик мусора
using (PaintMap p = new PaintMap())
{
System.Drawing.Image i = p.AddLineToMap("1");
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(i, 8419, 5953);
IntPtr hBitmap = bmp.GetHbitmap();
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
Image2.ImageSource = bitmapSource;
DeleteObject(hBitmap);
System.GC.Collect();
}
Это отличный (!!) пост, хотя со всеми комментариями и предложениями мне потребовался час, чтобы разобраться в деталях. Итак, вот вызов для получения BitMapSource с SafeHandles, а затем пример его использования для создания файла изображения.PNG. В самом низу "употребления" и некоторые ссылки. Конечно, это не моя заслуга, я просто писец.
private static BitmapSource CopyScreen()
{
var left = Screen.AllScreens.Min(screen => screen.Bounds.X);
var top = Screen.AllScreens.Min(screen => screen.Bounds.Y);
var right = Screen.AllScreens.Max(screen => screen.Bounds.X + screen.Bounds.Width);
var bottom = Screen.AllScreens.Max(screen => screen.Bounds.Y + screen.Bounds.Height);
var width = right - left;
var height = bottom - top;
using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
BitmapSource bms = null;
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
IntPtr hBitmap = new IntPtr();
var handleBitmap = new SafeHBitmapHandle(hBitmap, true);
try
{
bmpGraphics.CopyFromScreen(left, top, 0, 0, new System.Drawing.Size(width, height));
hBitmap = screenBmp.GetHbitmap();
using (handleBitmap)
{
bms = Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
} // using
return bms;
}
catch (Exception ex)
{
throw new ApplicationException($"Cannot CopyFromScreen. Err={ex}");
}
} // using bmpGraphics
} // using screen bitmap
} // method CopyScreen
Вот использование, а также класс "Безопасная ручка":
private void buttonTestScreenCapture_Click(object sender, EventArgs e)
{
try
{
BitmapSource bms = CopyScreen();
BitmapFrame bmf = BitmapFrame.Create(bms);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bmf);
string filepath = @"e:\(test)\test.png";
using (Stream stm = File.Create(filepath))
{
encoder.Save(stm);
}
}
catch (Exception ex)
{
MessageBox.Show($"Err={ex}");
}
}
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteObject(IntPtr hObject);
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return DeleteObject(handle) > 0;
}
}
И, наконец, посмотрите на мои "усы":
using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Security;
Библиотеки DLL, на которые есть ссылки: * PresentationCore * System.Core * System.Deployment * System.Drawing * WindowsBase
У меня есть решение для тех, кто хочет загрузить изображение из памяти или другого класса
public static InteropBitmap Bitmap2BitmapImage(Bitmap bitmap)
{
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
return (InteropBitmap)source;
}
catch (Exception e)
{
MessageBox.Show("Convertion exception: " + e.Message + "\n" +e.StackTrace);
return null;
}
}
И тогда я использую его установить источник изображения
CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);
Изображение следующее определение
<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
Width="{Binding Width}"
Height="{Binding Height}">
</Image>