WPF CreateBitmapSourceFromHBitmap()内存泄漏

时间:2009-10-09 21:17:42

标签: c# .net wpf memory-leaks

我需要逐个像素地绘制图像并将其显示在WPF中。我尝试使用System.Drawing.Bitmap然后使用CreateBitmapSourceFromHBitmap()为WPF图像控件创建BitmapSource来尝试执行此操作。我在某处有内存泄漏,因为当重复调用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内存?

6 个答案:

答案 0 :(得分:75)

MSDN for 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来电。

答案 1 :(得分:21)

每当处理非托管句柄时,使用“安全句柄”包装器都是个好主意:

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

答案 2 :(得分:5)

我有相同的要求和问题(内存泄漏)。我实施了标记为答案的相同解决方案。但是虽然解决方案有效,但它对性能造成了不可接受的打击。在i7上运行,我的测试应用程序看到了稳定的30-40%CPU,200-400MB RAM增加,垃圾收集器几乎每毫秒运行一次。

由于我正在进行视频处理,因此我需要更好的性能。我想出了以下内容,以为我会分享。

可重用的全局对象

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;

实施例

SELECT TOP (1) PicsID from Pics WHERE (PicsID < 130)

结果是稳定的10-13%CPU,70-150MB RAM,垃圾收集器在6分钟的运行中仅运行两次。

答案 3 :(得分:0)

这是一篇很棒的(!!)帖子,虽然有了所有的意见和建议,我花了一个小时来弄清楚这些内容。所以这里是调用BitMapSource获取SafeHandles,然后使用它来创建.PNG图像文件。最底层是&#39;使用&#39;和一些参考文献。当然,没有一个信用是我的,我只是抄写员。

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

以下是用法,以及&#34; Safe Handle&#34;类:

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

最后看看我的使用&#39;:

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

答案 4 :(得分:0)

对于我来说,它不能直接使用此方法。我还必须添加一个干净的垃圾收集器

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

答案 5 :(得分:-1)

对于想要从内存或其他类中加载图像的人,我有一个解决方案

 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>
相关问题