WPF CreateBitmapSourceFromHBitmap()内存泄漏WPF CreateB

2019-05-06 12:17发布

我需要通过像素绘制一个图像像素并显示它一个WPF内部。 我试图通过使用要做到这一点System.Drawing.Bitmap然后用CreateBitmapSourceFromHBitmap()创建BitmapSource的WPF Image控件。 我有内存泄漏的地方,因为当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内存?

Answer 1:

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的语句。



Answer 2:

每当与非托管手柄处理它可以使用“安全把手”包装是个好主意:

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方法。



Answer 3:

我有同样的要求和问题(内存泄漏)。 我实现了相同的解决方案标记为答案。 但是,尽管该解决方案的工作原理,它造成不可接受的命中性能。 运行在一个i7处理器,我的测试应用程序看到一个稳定的30-40%的CPU,RAM 200-400MB增加,垃圾收集器正在运行几乎每毫秒。

自从我做视频处理,我需要更好的性能。 我想出了下面,所以以为我也有同感。

可重复使用的全局对象

//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%的CPU,RAM 70-150MB,和垃圾收集器仅在运行6分钟运行两次。



Answer 4:

这是一个伟大的(!!)后,虽然所有的意见和建议,我花了一个小时才弄清楚件。 因此,这里是一个电话与SafeHandles得到的BitmapSource,然后它的一个例子使用,以创建一个PNG图片文件。 在最底层是“usings”和一些参考。 当然,没有信用的是我的,我只是抄写员。

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;
    }
}

最后来看看我的“usings”:

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



Answer 5:

我对别人的解决方案谁想要从内存或其他类加载图像

 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>


文章来源: WPF CreateBitmapSourceFromHBitmap() memory leak