Out of Memory from capuring screenshots

2019-02-19 06:56发布

问题:

Hello there I have a problem. I have a setup to capture Screenshots of my WebBrowser control:

public static class Utilities
{
    public const int SRCCOPY = 13369376;

    public static Image CaptureScreen()
    {
        return CaptureWindow(User32.GetDesktopWindow());
    }

    public static Image CaptureWindow(IntPtr handle)
    {

        IntPtr hdcSrc = User32.GetWindowDC(handle);

        User32.RECT windowRect = new User32.RECT();
        User32.GetWindowRect(handle, ref windowRect);

        int width = windowRect.right - windowRect.left;
        int height = windowRect.bottom - windowRect.top;

        IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
        IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);

        IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
        Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, SRCCOPY);
        Gdi32.SelectObject(hdcDest, hOld);
        Gdi32.DeleteDC(hdcDest);
        User32.ReleaseDC(handle, hdcSrc);

        Image image = Image.FromHbitmap(hBitmap);
        Gdi32.DeleteObject(hBitmap);

        return image;
    }
}

public static class ControlExtensions
{
    public static Image DrawToImage(this Control control)
    {
        return Utilities.CaptureWindow(control.Handle);
    }
}

public class Gdi32
{
    [DllImport("gdi32.dll")]
    public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
    [DllImport("gdi32.dll")]
    public static extern bool DeleteDC(IntPtr hDC);
    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    [DllImport("gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
}

public static class User32
{
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport("user32.dll")]
    public static extern IntPtr GetDesktopWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
    [DllImport("user32.dll")]
    public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
}

And I have a timer on 100ms to show the WebBrowser at 10fps in a picturebox:

    private void timer1_Tick(object sender, EventArgs e)
    {
        pictureBox2.Image = ControlExtensions.DrawToImage(webBrowser1);
    }

But from this goes my memory 100mb up per secound.

Did I miss something that clears the memory?

//EDIT @N4TKD now i got it:

        if (pictureBox2.Image != null)
            pictureBox2.Image.Dispose();
        pictureBox2.Image = ControlExtensions.DrawToImage(webBrowser1);

thank you

回答1:

The garbage collector will clean up after you, but it can't get the job done properly for the Image class. Which is a very small managed wrapper class around a big pile of unmanaged memory that stores the pixel data of an image. You will run out of memory before you've created enough Image objects to trigger a GC.

Explicitly disposing the old images is required.



回答2:

My GDI is a bit rusty on order of operations... but have you tried moving Gdi.DeleteDC(hdcDest) after Gdi32.DeleteObject(hBitmap)?

It would seem like DeleteDC should be the last thing you do after cleaning up all associated objects with that DC.

Here is how I would think to reorder it:

    IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
    Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, SRCCOPY);

    Image image = Image.FromHbitmap(hBitmap);

    Gdi32.SelectObject(hdcDest, hOld);
    Gdi32.DeleteObject(hBitmap);

    Gdi32.DeleteDC(hdcDest);
    User32.ReleaseDC(handle, hdcSrc);