none
Need help confirming possible bug in Graphics.CopyFromScreen regarding leakage of Gdi object handle

    Question

  • If this isn't the right newsgroup for this, please let me know, and if possible, which one it would be better suited in.

    I have discovered what looks like a bug in Graphics.CopyFromScreen, which seems to leak 1 gdi object each time it is called.

    Here is a sample program to demonstrate:
    Compile as a console app, and add a reference to System.Drawing.


    using System;
    using System.Drawing;
    using System.Threading;

    namespace TestProgram1
    {
        class Program
        {
            public static void Main()
            {
                Bitmap screenCopy = new Bitmap(256, 256);
                while (true)
                {
                    Console.Out.WriteLine("Copying bitmap from screen");
                    using (Graphics g = Graphics.FromImage(screenCopy))
                    {
                        g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
                    }

                    Thread.Sleep(1000);
                }
            }
        }
    }

    I used Iarsn TaskInfo to observe the program and can see that each time CopyFromScreen is called, TaskInfo increases the number of gdi handles the program has. If I comment out just the CopyFromScreen line, this increase does not happen.

    I noticed the above problem when my program, which makes snapshots of the screen repeatedly, crashed after about 10 minutes. It crashed with one of those hard exceptions containing registers etc.

    I then started analysing leaks since I do a lot of GDI manipulation and noticed the above.

    The way I see it, there are three possible reasons:

    1. There really is a bug in Graphics.CopyFromScreen
    2. The code above is buggy, ie. I need to do it in a different way
    3. The program I use to notice the gdi objects has a problem

    I am running this under Visual Studio 2008, with .NET 3.5 installed. I have not installed the SP1 betas for .NET 3.5 and VS2008.

    Can anyone else confirm my findings before I potentially embarass myself by filing a bug in my own code with Microsoft? Smile

    Any help will be appreciated.
    Sunday, May 18, 2008 12:43 PM

Answers

  • Confirmed, I see it leaking one device context handle per call.  Trying to reproduce the leak with equivalent code in C#:

    using System.Runtime.InteropServices;
    using System.Reflection;
    ...
        [DllImport("gdi32.dll", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
        public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);

          Bitmap screenCopy = new Bitmap(256, 256);
          using (Graphics gdest = Graphics.FromImage(screenCopy))
          while (true) {
            //g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
            using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero)) {
              IntPtr hSrcDC = gsrc.GetHdc();
              IntPtr hDC = gdest.GetHdc();
              int retval = BitBlt(hDC, 0, 0, screenCopy.Width, screenCopy.Height, hSrcDC, 0, 0, (int)CopyPixelOperation.SourceCopy);
              gdest.ReleaseHdc();
              gsrc.ReleaseHdc();
            }
          }

    Didn't produce the leak.  Graphics.CopyFromScreen() uses an internal class named System. Drawing. Internal. DeviceContext to create a device context for the screen.  Rewriting the above code to use the DeviceContext class is tricky since it is internal.  Reflection can break that barrier:

          Assembly asm = Assembly.GetAssembly(typeof(Size));
          Type t = asm.GetType("System.Drawing.Internal.DeviceContext");
          ConstructorInfo dcNew = t.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
            null, new Type[] { typeof(IntPtr) }, null);
          MethodInfo dcDispose = t.GetMethod("Dispose");
          PropertyInfo dcGetHdc = t.GetProperty("Hdc");
          FieldInfo dchDC = t.GetField("hDC", BindingFlags.NonPublic | BindingFlags.Instance);
          Bitmap screenCopy = new Bitmap(256, 256);
          using (Graphics gdest = Graphics.FromImage(screenCopy))
          while (true) {
            object dc = dcNew.Invoke(new object[] { IntPtr.Zero });
            IntPtr hSrcDC = (IntPtr)dcGetHdc.GetValue(dc, null);
            IntPtr hDC = gdest.GetHdc();
            int retval = BitBlt(hDC, 0, 0, screenCopy.Width, screenCopy.Height, hSrcDC, 0, 0, (int)CopyPixelOperation.SourceCopy);
            gdest.ReleaseHdc();
            dcDispose.Invoke(dc, null);
            if (IntPtr.Zero != (IntPtr)dchDC.GetValue(dc)) break;
          }

    Bingo, the leak is back.  Definitely the DeviceContext class that is leaking.  Its Dispose() method should release the device context handle and I check for it by verifying the internal hDC field.  That works as expected. 

    You have to be careful not to look at the "dc" object with the debugger, the Hdc property dynamically creates a new handle when its getter is invoked.  I think that's the source of the problem.  DeviceContext objects are getting cached in a static class instance named System. Drawing. Internal. DeviceContext.  Its AddDeviceContext method is called by the DeviceContext constructor.  It has a RemoveDeviceContext method that removes the instance from the cache.  It calls GetHashCode() on the object and that's bad news.  DeviceContext.GetHashCode() calls the Hdc property getter which will create a *new* handle when the original handle is null.  I have no idea who is responsible for calling RemoveDeviceContext().

    Anyhoo, you've got the confirmation and a workaround.  You can post the bug at the Connect web site.
    Sunday, May 18, 2008 3:20 PM

All replies

  • Confirmed, I see it leaking one device context handle per call.  Trying to reproduce the leak with equivalent code in C#:

    using System.Runtime.InteropServices;
    using System.Reflection;
    ...
        [DllImport("gdi32.dll", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
        public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);

          Bitmap screenCopy = new Bitmap(256, 256);
          using (Graphics gdest = Graphics.FromImage(screenCopy))
          while (true) {
            //g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
            using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero)) {
              IntPtr hSrcDC = gsrc.GetHdc();
              IntPtr hDC = gdest.GetHdc();
              int retval = BitBlt(hDC, 0, 0, screenCopy.Width, screenCopy.Height, hSrcDC, 0, 0, (int)CopyPixelOperation.SourceCopy);
              gdest.ReleaseHdc();
              gsrc.ReleaseHdc();
            }
          }

    Didn't produce the leak.  Graphics.CopyFromScreen() uses an internal class named System. Drawing. Internal. DeviceContext to create a device context for the screen.  Rewriting the above code to use the DeviceContext class is tricky since it is internal.  Reflection can break that barrier:

          Assembly asm = Assembly.GetAssembly(typeof(Size));
          Type t = asm.GetType("System.Drawing.Internal.DeviceContext");
          ConstructorInfo dcNew = t.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
            null, new Type[] { typeof(IntPtr) }, null);
          MethodInfo dcDispose = t.GetMethod("Dispose");
          PropertyInfo dcGetHdc = t.GetProperty("Hdc");
          FieldInfo dchDC = t.GetField("hDC", BindingFlags.NonPublic | BindingFlags.Instance);
          Bitmap screenCopy = new Bitmap(256, 256);
          using (Graphics gdest = Graphics.FromImage(screenCopy))
          while (true) {
            object dc = dcNew.Invoke(new object[] { IntPtr.Zero });
            IntPtr hSrcDC = (IntPtr)dcGetHdc.GetValue(dc, null);
            IntPtr hDC = gdest.GetHdc();
            int retval = BitBlt(hDC, 0, 0, screenCopy.Width, screenCopy.Height, hSrcDC, 0, 0, (int)CopyPixelOperation.SourceCopy);
            gdest.ReleaseHdc();
            dcDispose.Invoke(dc, null);
            if (IntPtr.Zero != (IntPtr)dchDC.GetValue(dc)) break;
          }

    Bingo, the leak is back.  Definitely the DeviceContext class that is leaking.  Its Dispose() method should release the device context handle and I check for it by verifying the internal hDC field.  That works as expected. 

    You have to be careful not to look at the "dc" object with the debugger, the Hdc property dynamically creates a new handle when its getter is invoked.  I think that's the source of the problem.  DeviceContext objects are getting cached in a static class instance named System. Drawing. Internal. DeviceContext.  Its AddDeviceContext method is called by the DeviceContext constructor.  It has a RemoveDeviceContext method that removes the instance from the cache.  It calls GetHashCode() on the object and that's bad news.  DeviceContext.GetHashCode() calls the Hdc property getter which will create a *new* handle when the original handle is null.  I have no idea who is responsible for calling RemoveDeviceContext().

    Anyhoo, you've got the confirmation and a workaround.  You can post the bug at the Connect web site.
    Sunday, May 18, 2008 3:20 PM
  • Thanks.

    I had already rewritten my code to use interop by the time I got to submit the post above, just wanted to know if there was anything that I wasn't seeing or doing.

    I'll post most, if not all, of your post as information as well since you've narrowed it down to what specifically is wrong whereas I was more on the "something isn't right" level of digging Smile

    Thanks.
    Sunday, May 18, 2008 3:26 PM
  • Bug posted, https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=344752
    It might be a dupe of bug 264527, but since I'm not 100% certain, I posted a new one, and just noted the possibility in the bug report.
    Sunday, May 18, 2008 3:46 PM
  • Not the same bug but definitely related.  Smells like a classic regression bug.  As they fixed the ErrorProvider leak (somebody now calling RemoveDeviceContext), they broke CopyFromScreen().
    Sunday, May 18, 2008 4:12 PM
  •  

    And I've spent the last month wondering what was wrong with my code...

    It's also leaking in VS 2005.

     

    I'm somewhat new with C#, and I don't really understand the workaround. Care to elaborate some more ?

     

    That is my function to get the color of a pixel:

     

    public Color GetColor(int x, int y)

    {

    Bitmap b = new Bitmap( 1, 1);

    Graphics g = Graphics.FromImage(b);

    g.CopyFromScreen(x, y, 0, 0, b.Size );

    g.Dispose();

    Color c = b.GetPixel(0, 0);

    b.Dispose();

    return c;

    }

    Tuesday, May 20, 2008 12:57 AM