'IntPtr - Memory & GDI leak [C#]

The Problem: When taking Screenshots of a Screen (in a loop) I get a RAM and GDI leak.

private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
    {
        
        int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
        int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;

        IntPtr dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
        //Create the DC of the display
        Graphics g1 = Graphics.FromHdc(dc1);
        //Create a new Graphics object from the handle of a specified device
        Bitmap MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
        //Create a Bitmap object of the same size according to the screen size
        Graphics g2 = Graphics.FromImage(MyImage);

        //Get the handle of the screen
        IntPtr dc3 = g1.GetHdc();
        //Get the handle of the bitmap
        IntPtr dc2 = g2.GetHdc();
        BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc3, ScreenLocWidth, ScreenLocHeight,
            (int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
        g1.ReleaseHdc(dc3);
        //Release the screen handle
        g2.ReleaseHdc(dc2);
        //Release the bitmap handle
        DeleteObject(dc1);
        DeleteObject(dc2);
        DeleteObject(dc3);
        
        return MyImage;
    }

Debugging gave me these lines which are potentially causing the leak.

//Get the handle of the screen
        IntPtr dc3 = g1.GetHdc();
        //Get the handle of the bitmap
        IntPtr dc2 = g2.GetHdc();

With the following I am trying to release and delete the objects created, with no effect.

g1.ReleaseHdc(dc3);
    //Release the screen handle
    g2.ReleaseHdc(dc2);
    //Release the bitmap handle
    DeleteObject(dc1);
    DeleteObject(dc2);
    DeleteObject(dc3);

I found a solution using the GarbageCollector. That works! No more memory nor GDI leak. I simply call

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

after I call "GetSS". But I would like to understand why releasing and deleting the objects manually doesn't work, I want to avoid using the GarbageCollector at all if possible.

EDIT: This is how I call GetSS

while (startLoc.x == 0) 
        {
            using (Bitmap imgScene = GetSS(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Screen.PrimaryScreen.Bounds.Width, (int)(Screen.PrimaryScreen.Bounds.Height * 0.20)))
            {
                //the stuff I do with the image is commented out for testing purposes, this is not causing th leak
            }
            Thread.Sleep(10);
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        }

And this is for deleting the Object:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);

Stay healthy everyone.



Solution 1:[1]

If a forced GC solves the problem, it is probably due to some finalizer kicking in and freeing memory. That hints that it might be some disposable object not being disposed. The Graphics class are IDisposable, so they should be inside a using statement to ensure disposal. The bitmap seem to be correctly disposed outside the function.

This suggest that the corresponding function for CreateDC is DeleteDC.

I might also recommend releasing all resources inside finally-statements, to ensure they are disposed even if some exception occur.

Solution 2:[2]

You are missing using blocks, and also DeleteObject should be DeleteDC, which should also be in a finally.

Also, dc3 is not necessary as you have that already in dc1.

 private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
 {
     int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
     int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;

     Bitmap MyImage;
     IntPtr dc1 = IntPtr.Zero;
     IntPtr dc2 = IntPtr.Zero;
     try
     {
         dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
         //Create the DC of the display
         //Create a Bitmap object of the same size according to the screen size
         using (Graphics g1 = Graphics.FromHdc(dc1))
         {
             MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
             using (Graphics g2 = Graphics.FromImage(MyImage))
             {
                 //Get the handle of the bitmap
                 dc2 = g2.GetHdc();
                 BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc1, ScreenLocWidth, ScreenLocHeight,
                     (int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
             }
         }
     }
     catch
     {
         MyImage?.Dispose();
         throw;
     }
     finally
     {
         //Release the bitmap handle
         if (dc1 != IntPtr.Zero)
             DeleteObject(dc1);
         if (dc2 != IntPtr.Zero)
             g2.ReleaseHdc(dc2);
     }
     
     return MyImage;
 }

Don't forget that the image you return also must be disposed at some point.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JonasH
Solution 2 Charlieface