0

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#)

Leonid Malyshev
  • 475
  • 1
  • 6
  • 14
  • http://stackoverflow.com/questions/1163761/capture-screenshot-of-active-window – Uthistran Selvaraj Aug 03 '16 at 07:47
  • @Uthistran S. The question is not about how to make screenshot (in fact i've used codes from your link or similar. But about memory leaks. And it's mandatory to get the result as a `BitmapSource`. Not just to save to a file as in your link. – Leonid Malyshev Aug 03 '16 at 07:54
  • 1
    Is the `BitmapSource` object being disposed of? – TheLethalCoder Aug 03 '16 at 08:08
  • @TheLethalCoder In demo code I don't use the result of the function. I just call the function in a cycle. So disposing of unused memory is under GC control. But memory leaks... – Leonid Malyshev Aug 03 '16 at 08:46

2 Answers2

1

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();
        }
    }
}
  • Yes. I can move try/catch. But it doesn't solve the problem. As I wrote the problem is in `CreateBitmapSourceFromHBitmap`. Commenting the operator solves the problem (but I lose the result I need). – Leonid Malyshev Aug 03 '16 at 08:32
  • Only a double check: are you finding the memory leak after the closing brace of the using screenBmp? i.e. at the return result line? Since only after that closing brace you are sure that the screenBmp is disposed. –  Aug 03 '16 at 08:35
  • Yes. I see memory leaks in the application - not after a particular line of code. I just use performance monitor and call the function in a cycle (in the demo I don't use the result returned) – Leonid Malyshev Aug 03 '16 at 08:49
  • I'm forcing `GC.Collect();` before `return result;`and it doesn't seem leaking –  Aug 03 '16 at 09:16
1

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.

Sergey L
  • 1,402
  • 1
  • 9
  • 11