Leonid Malyshev
Leonid Malyshev

Reputation: 475

Memory leak in Imaging.CreateBitmapSourceFromHBitmap

I have next function (makes screenshot)

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

And cannot avoid memory leak which is a result of calling Imaging.CreateBitmapSourceFromHBitmap. As I call this function in a cycle this memory leak is very important for me. Called in WPF application (Windows, c#)

Upvotes: 0

Views: 459

Answers (2)

user6996876
user6996876

Reputation:

As you already know, you have to Dispose() screenBmp.

You are actually calling it by an using statement, so that should be fine, but I suspect the try/catch could interfere.

Do you have a chance to move the try/catch so that only the CopyFromScreen and CreateBitmapSourceFromHBitmap are surrounded?

From comments

Since only after that closing brace of the using statement you are sure that the screenBmp can be disposed, I'm forcing a GC collect there

GC.Collect(); 
return result;

and it doesn't seem leaking.

Here is my demo

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

Upvotes: 1

Sergey L
Sergey L

Reputation: 1492

As you are working with bitmaps (screen size) it means expected data size is bigger than 85000 bytes. The objects of such sizes are treated differently by GC. It is called LOH. See https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/, it was improved in 4.5 https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/ But the problem is still here. Accounting huge objects with high frequency leads to significant increase of memory usage of your application. There're 2 problem leads to it: 1) GC does not work immediatly, it takes time before it started freeing memory; 2) fragmentation of LOH (see the first article), this is why it is not freed and this is why you can see the memory usage is increased.

Possible solutions: 1) Use server GC and concurent GC; force GC manually. Most likely it does not help greatly. 2) Re-use existing object(allocated memory) instead of creating new Bitmap and Graphics all the time in a loop. 3) Switch to use Windows API directly and handle allocations manually.

Upvotes: 1

Related Questions