Search code examples
c#wpfbitmapsourcewritablebitmap

OutOfMemory exception with BitmapSource in WPF app


Task: I got 2 monitors. And I need to show on #1 what is going on #2. In another words, first monitor is nothing but a reflector of second.

Current solution: Just making screenshot every ~100ms and re-render. Following method is responsible for capturing screenshots:

private BitmapSource MakeScreenshot(Screen screen)
    {
        using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
        {
            using (var bmpGraphics = Graphics.FromImage(screenBmp))
            {
                bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);

                return 
                    Imaging.CreateBitmapSourceFromHBitmap(
                        screenBmp.GetHbitmap(),
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());
            }
        }
    }

After that I uset Start(...) method to run my "reflection" from second screen to first:

public void Start(int delay, int period)
    {
        if (_timer != null) throw new InvalidOperationException();

        _timer = new System.Threading.Timer(
            _ =>
            {
                _placeholder
                    .Dispatcher
                    .Invoke(() =>
                    {
                        _placeholder.Source = MakeScreenshot(_targetScreen); // re-render new screenshot
                    });
            }, 
            null, 
            delay, 
            period);
    }

Problem: After around 30-40 second of pretty nice run it fails with OutOfMemoryException. I've investigated some of posts here, but found nothing regarding my problem.


Solution

  • That is because you leak memory here:

    Imaging.CreateBitmapSourceFromHBitmap(
        screenBmp.GetHbitmap(), // < here
        IntPtr.Zero,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
    

    You need to free memory used by GDI bitmap after you call screenBmp.GetHbitmap(). Change that like this:

    private BitmapSource MakeScreenshot(Screen screen)
    {
        using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
        {
            using (var bmpGraphics = Graphics.FromImage(screenBmp))
            {
                bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);
                var handle = screenBmp.GetHbitmap();
                try {
                    return
                        Imaging.CreateBitmapSourceFromHBitmap(
                            handle,
                            IntPtr.Zero,
                            Int32Rect.Empty,
                            BitmapSizeOptions.FromEmptyOptions());
                }
                finally {
                    DeleteObject(handle);
                }
            }
        }
    }
    
    [System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    

    And it should not leak any more.