WPF CreateBitmapSourceFromHBitmap()内存泄漏

Mr *_*ell 46 .net c# wpf memory-leaks

我需要逐个像素地绘制图像并将其显示在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;
}
Run Code Online (Sandbox Code Playgroud)

我该怎么做才能释放BitmapSource记忆?

Jul*_*ain 77

MSDNBitmap.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我还用声明取代了你的Dispose()电话using.


Jac*_*eja 22

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

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

    protected override bool ReleaseHandle()
    {
        return GdiNative.DeleteObject(handle) > 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

只要你展示一个句柄就构造一个(理想情况下你的API永远不会暴露IntPtr,它们总是返回安全句柄):

IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

using (handle)
{
  ... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
Run Code Online (Sandbox Code Playgroud)

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

  • “答案”指向了正确的方向,但仍然没有奏效——我仍然记忆犹新——但你的解决方案完美无缺——不仅如此,我还喜欢以这种方式包装——这是真正的抽象和编码的未来——抱歉被带走了 (2认同)

小智 5

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

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

可重用的全局对象

//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);
Run Code Online (Sandbox Code Playgroud)

转换代码

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); 
}
Run Code Online (Sandbox Code Playgroud)

实施例

//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;
Run Code Online (Sandbox Code Playgroud)

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