C# WPF CreateBitmapSourceFromHBitmap()内存泄漏
我需要画一个像素的图像像素和显示在一个WPF。我尝试使用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; i &
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;
}
for(int i=0;i<100;i++)
{
var bmp=新系统.Drawing.Bitmap(10001000);
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;
}
如何释放位图源
内存?状态:
备注
//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);
您负责调用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,它们总是返回安全句柄):
并像这样使用它:
using (handle)
{
... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
SafeHandle底座为您提供了一个自动的一次性/终结器模式,您只需覆盖ReleaseHandle方法。我也有同样的要求和问题(内存泄漏)。我实现了与标记为答案相同的解决方案。但是,尽管解决方案可行,但它对性能造成了不可接受的影响。在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);
转换代码
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;
实施示例
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,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
以下是用法,以及“安全句柄”类:
最后看看我的“用法”:
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>
在我的例子中,它不能直接使用此方法。此外,我还必须添加一个干净的垃圾收集器
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,而您的解决方案非常有效。感谢你写了一篇很好的关于我应该更了解的东西的小文章。“答案”指向了正确的方向,但仍然不起作用-我仍然没有记性-但你的解决方案工作完美无瑕-不仅如此,我还喜欢以这种方式包装-这是真正的抽象和编码的未来-抱歉被冲昏头脑了不,抱歉,我无法纠正你的错误。基于您的错误,我认为您试图直接访问位图。看,现在的情况是,您从Kinect流复制位图并将其写入您自己的可写位图,所有这些都在转换代码中。请尝试仔细检查锁定和解锁的顺序,在位图->位图数据->可写位图之间移动,以及矩形的大小是否正确(包括z轴=字节/像素)。幸运的是,代码与问题中的代码基本相同。它根本无法解决内存泄漏问题。计算屏幕边界的代码(
screen.AllScreens.Min/Max
)可以替换为SystemParameters.VirtualScreenLeft/Top/Width/Height
)。这消除了引用System.Windows.Forms的需要,它不是WPF的一部分,并且使代码更快,尤其是当您有多个屏幕时。