Imaging.CreateBitmapSourceFromHBitmap内存泄漏

本文关键字:泄漏 内存 CreateBitmapSourceFromHBitmap Imaging | 更新日期: 2023-09-27 17:59:49

我有下一个功能(制作屏幕截图)

[DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);
    private Screen SavedScreen { get; } = Screen.PrimaryScreen;
    private BitmapSource CopyScreen()
    {
        try
        {
            BitmapSource result;
            using (
                var screenBmp = new Bitmap(SavedScreen.Bounds.Width, SavedScreen.Bounds.Height, PixelFormat.Format32bppArgb))
            {
                using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
                {
                    bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
                        CopyPixelOperation.SourceCopy);
                    IntPtr hBitmap = screenBmp.GetHbitmap();
                    //********** Next line do memory leak
                    result = Imaging.CreateBitmapSourceFromHBitmap( hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                    DeleteObject(hBitmap);
                }
            }
            return result;
        }
        catch (Exception ex)
        {
            //ErrorReporting ($"Error in CopyScreen(): {ex}");
            Debugger.Break();
            return null;
        }
    }

并且不能避免由于调用Imaging.CreateBitmapSourceFromHBitmap而导致的内存泄漏。当我在一个循环中调用这个函数时,内存泄漏对我来说非常重要。在WPF应用程序(Windows,c#)中调用

Imaging.CreateBitmapSourceFromHBitmap内存泄漏

如您所知,您必须使用Dispose() screenBmp

实际上,您是通过using语句调用它的,因此应该是可以的,但我怀疑try/catch可能会干扰。

你有机会移动试跳/接球,这样只有CopyFromScreenCreateBitmapSourceFromHBitmap被包围吗?

来自评论

因为只有在using语句的右大括号之后,您才能确定screenBmp可以被处理,所以我在那里强制使用GC collect

GC.Collect(); 
return result;

并且它似乎没有泄漏。

这是我的演示

class Program
{
    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);
    private static Screen SavedScreen { get; } = Screen.PrimaryScreen;
    private static BitmapSource CopyScreen()
    {
        //try
        //{
        BitmapSource result;
        using (
            var screenBmp = new Bitmap(200, 100))
        {
            using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
            {
                bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
                    CopyPixelOperation.SourceCopy);
                IntPtr hBitmap = screenBmp.GetHbitmap();
                bmpGraphics.Dispose();
                //********** Next line do memory leak
                result = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                DeleteObject(hBitmap);
                //result = null;
            }
        }
        GC.Collect();
        return result;
        //}
        //catch (Exception ex)
        //{
        //    //ErrorReporting ($"Error in CopyScreen(): {ex}");
        //    Console.WriteLine(ex.Message);
        //    Debugger.Break();
        //    return null;
        //}
    }
    static void Main(string[] args)
    {
        for (int i = 0; i < 100000; i++)
        {
            Thread.Sleep(100);
            var test = CopyScreen();
        }
    }
}

当您使用位图(屏幕大小)时,这意味着预期的数据大小大于85000字节。GC会对这种大小的对象进行不同的处理。它被称为LOH。看见https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/,在4.5中有所改进https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/但问题仍然存在。对高频率的大型对象进行会计处理会显著增加应用程序的内存使用量。有两个问题导致了它:1)GC不能立即工作,它需要一段时间才能开始释放内存;2) LOH的碎片化(参见第一篇文章),这就是为什么它没有被释放,这也是为什么你可以看到内存使用量增加的原因。

可能的解决方案:1) 使用服务器GC和并发GC;手动强制GC。很可能它没有太大帮助。2) 重复使用现有对象(分配的内存),而不是一直在循环中创建新的位图和图形。3) 切换为直接使用Windows API并手动处理分配。