'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 |