Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# WPF CreateBitmapSourceFromHBitmap()内存泄漏_C#_.net_Wpf_Memory Leaks - Fatal编程技术网

C# WPF CreateBitmapSourceFromHBitmap()内存泄漏

C# WPF CreateBitmapSourceFromHBitmap()内存泄漏,c#,.net,wpf,memory-leaks,C#,.net,Wpf,Memory Leaks,我需要画一个像素的图像像素和显示在一个WPF。我试图通过使用System.Drawing.Bitmap,然后使用CreateBitmapSourceFromHBitmap为WPF图像控件创建BitmapSource来完成此操作。我有一个内存泄漏的地方,因为当CreateBitmapSourceFromBitmap被反复调用时,内存使用量会上升,直到应用程序结束才会下降。如果我不调用CreateBitmapSourceFromBitmap,则内存使用率没有明显变化 for (int i = 0;

我需要画一个像素的图像像素和显示在一个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;
}
如何释放位图源内存?

状态:

评论

您负责调用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);
    }
}
我还用using语句替换了Dispose调用。

声明:

评论

您负责调用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 base为您提供了一个自动的一次性/终结器模式,您只需重写ReleaseHandle方法。

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

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%的CPU,200-400MB的RAM增加,垃圾收集器几乎每毫秒运行一次

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

可重用全局对象

转换码

实现示例


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

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

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

可重用全局对象

转换码

实现示例


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

这是一个很棒的!!虽然有很多评论和建议,但我花了一个小时才弄明白其中的要点。下面是一个调用,用于获取带有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
以下是用法以及安全句柄类:

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包括: *展示中心 *系统核心 *系统部署 *系统图
*WindowsBase

这是一个很棒的!!虽然有很多评论和建议,但我花了一个小时才弄明白其中的要点。下面是一个调用,用于获取带有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
以下是用法以及安全句柄类:

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包括: *展示中心 *系统核心 *系统部署 *系统图
*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>

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

 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>

在我的例子中,这种方法不能直接起作用。此外,我还必须添加一个干净的垃圾收集器

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

在我的例子中,这种方法不能直接起作用。我必须加一个干净的g 除此之外,arbage收集器

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

这很有效。测试后会保留一些剩余内存,但垃圾收集器会将其收集起来。谢谢朱利安,你太棒了。很长一段时间以来,我一直在努力消除这个bug,而您的解决方案非常有效。谢谢你的工作。测试后会保留一些剩余内存,但垃圾收集器会将其收集起来。谢谢朱利安,你太棒了。很长一段时间以来,我一直在努力消除这个bug,而您的解决方案非常有效。感谢你写了一篇非常好的关于我应该更了解的东西的小文章。答案指向了正确的方向,但仍然不起作用-我仍然没有记性-但是你的解决方案完美地工作了-不仅如此,我还喜欢以这种方式包装-这是真正的抽象和编码的未来-抱歉,我太高兴了关于一些我应该知道得更多的东西的小文章。答案指向了正确的方向,但仍然不起作用-我仍然没有记忆-但你的解决方案工作完美-不仅如此,而且我喜欢以这种方式包装-这是真正的抽象和编码的未来-抱歉被冲昏头脑了,抱歉,我无法纠正你的错误。基于您的错误,我认为您试图直接访问位图。看,现在的情况是,您从Kinect流复制位图并将其写入您自己的可写位图,所有这些都在转换代码中。请尝试仔细检查锁定和解锁的顺序,在位图->位图数据->可写位图之间移动,以及矩形的大小是否正确(包括z轴=字节/像素)。好卢克诺,对不起,我无法纠正你的错误。基于您的错误,我认为您试图直接访问位图。看,现在的情况是,您从Kinect流复制位图并将其写入您自己的可写位图,所有这些都在转换代码中。请尝试仔细检查锁定和解锁的顺序,在位图->位图数据->可写位图之间移动,以及矩形的大小是否正确(包括z轴=字节/像素)。幸运的是,代码与问题中的代码基本相同。它根本不能解决内存泄漏问题。代码与问题中的代码基本相同。它根本无法解决内存泄漏问题。计算屏幕边界screen.AllScreens.Min/Max的代码可以替换为SystemParameters.VirtualScreenLeft/Top/Width/Height。这消除了引用System.Windows.Forms的需要,而System.Windows.Forms不是WPF的一部分,并且使代码速度更快,尤其是当您有多个屏幕时。用于计算屏幕边界的代码screen.AllScreens.Min/Max可以替换为SystemParameters.VirtualScreenLeft/Top/Width/Height。这消除了引用System.Windows.Forms的需要,它不是WPF的一部分,并且使代码更快,尤其是当您有多个屏幕时。