C# WPF CreateBitmapSourceFromHBitmap() 内存泄漏

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1546091/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 18:42:15  来源:igfitidea点击:

WPF CreateBitmapSourceFromHBitmap() memory leak

c#.netwpfmemory-leaks

提问by Mr Bell

I need to draw an image pixel by pixel and display it inside a WPF. I am attempting to do this by using a System.Drawing.Bitmapthen using CreateBitmapSourceFromHBitmap()to create a BitmapSourcefor a WPF Image control. I have a memory leak somewhere because when the CreateBitmapSourceFromBitmap()is called repeatedly the memory usage goes up and does not drop off until the application is ended. If I don't call CreateBitmapSourceFromBitmap()there is no noticeable change in memory usage.

我需要逐像素绘制图像并将其显示在 WPF 中。我试图通过使用System.Drawing.Bitmap然后使用为 WPF Image 控件CreateBitmapSourceFromHBitmap()创建一个来做到这一点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;
}

What can I do to free the BitmapSourcememory?

我该怎么做才能释放BitmapSource内存?

采纳答案by Julien Lebosquain

MSDN for Bitmap.GetHbitmap()states:

MSDNBitmap.GetHbitmap()状态:

Remarks

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.

评论

您负责调用 GDI DeleteObject 方法以释放 GDI 位图对象使用的内存。

So use the following code:

所以使用以下代码:

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

I also replaced your Dispose()call by an usingstatement.

我也用声明代替了你的Dispose()电话using

回答by Hyman Ukleja

Whenever dealing with unmanaged handles it can be a good idea to use the "safe handle" wrappers:

每当处理非托管句柄时,最好使用“安全句柄”包装器:

public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [SecurityCritical]
    public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
        : base(ownsHandle)
    {
        SetHandle(preexistingHandle);
    }

    protected override bool ReleaseHandle()
    {
        return GdiNative.DeleteObject(handle) > 0;
    }
}

Construct one like so as soon as you surface a handle (ideally your APIs would never expose IntPtr, they would always return safe handles):

一旦出现句柄,就像这样构造一个(理想情况下,您的 API 永远不会暴露 IntPtr,它们总是会返回安全句柄):

IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);

And use it like so:

并像这样使用它:

using (handle)
{
  ... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}

The SafeHandle base gives you an automatic disposable/finalizer pattern, all you need to do is override the ReleaseHandle method.

SafeHandle 基础为您提供了一个自动的一次性/终结器模式,您需要做的就是覆盖 ReleaseHandle 方法。

回答by TrickySituation

I had the same requirement and issue (memory leak). I implemented the same solution as marked as answer. But although the solution works, it caused an unacceptable hit to performance. Running on a i7, my test app saw a steady 30-40% CPU, 200-400MB RAM increases and the garbage collector was running almost every millisecond.

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

Since I'm doing video processing, I'm in need of much better performance. I came up with the following, so thought I would share.

因为我在做视频处理,所以我需要更好的性能。我想出了以下内容,所以我想分享一下。

Reusable Global Objects

可重用的全局对象

//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);

Conversion Code

转换代码

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

Implementation Example

实现示例

//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;

The result is a steady 10-13% CPU, 70-150MB RAM, and the garbage collector only ran twice in a 6 minute run.

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

回答by batpox

This is a great(!!) post, although with all the comments and suggestions, it took me an hour to figure out the pieces. So here is a call to get the BitMapSource with SafeHandles, and then an example usage of it to create a .PNG image file. At the very bottom are the 'usings' and some of the references. Of course, none of the credit is mine, I am just the scribe.

这是一个很棒的(!!)帖子,尽管有所有的评论和建议,但我花了一个小时才弄清楚这些部分。所以这里是一个使用 SafeHandles 获取 BitMapSource 的调用,然后是一个使用它来创建 .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

Here is the usage, and also the "Safe Handle" class:

这是用法,还有“安全句柄”类:

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

And finally a look at my '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;

The DLLs referenced included: * PresentationCore * System.Core * System.Deployment * System.Drawing * WindowsBase

引用的 DLL 包括:* PresentationCore * System.Core * System.Deployment * System.Drawing * WindowsBase

回答by Lionel Peeters

I have a solution for someone who want to load image from memory or other class

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

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

And then I use it the set the source of an image

然后我用它来设置图像的来源

CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);

Image is the following definition

图像是以下定义

<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
                Width="{Binding Width}"
                Height="{Binding Height}">
                </Image>

回答by Yannick Turbang

In my case it did not work directly with this method. I had to add a clean garbage collector in addition

在我的情况下,它不能直接使用这种方法。我不得不另外添加一个干净的垃圾收集器

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