4
votes

I am investigating a GDI resource leak in a large application. In order to further my understanding of how these problems occur, I have created a very small application which I have deliberately made 'leaky'. Here is a simple user control which should result in the creation of 100 Pen objects:

public partial class TestControl : UserControl
{
    private List pens = new List();

    public TestControl()
    {
        InitializeComponent();

        for (int i = 0; i < 100; i++)
        {
            pens.Add(new Pen(new SolidBrush(Color.FromArgb(255, i * 2, i * 2, 255 - i * 2))));
        }

        this.Paint += new PaintEventHandler(TestControl_Paint);
    }

    void TestControl_Paint(object sender, PaintEventArgs e)
    {
        for (int i = 0; i < 100; i++)
        {
            e.Graphics.DrawLine(pens[i], 0, i, Width, i);
        }
    }
}

However, when I create an instance of my object and add it to a form, looking at my application with TaskManager I currently see ~37 GDI objects. If I repeatedly add new TestObject user controls to my form, I still only see ~37 GDI objects.

What is going on here! I thought that the constructor for System.Drawing.Pen would use the GDI+ API to create a new Pen, thus using a new GDI object.

I must be going nuts here. If I cannot write a simple test application that creates GDI objects, how can I create one which leaks them!

Any help would be much appreciated.

Best Regards, Colin E.

6
Strange problem there, I agree. Have you verified whether the no. of GDI objects decreases from 37 if you loop fewer than 37 times?Noldorin
Yes - the 37 GDI objects appears to relate to the overhead of the simple test application itself. It is nothing to do with the number of loops in the above code. I think OregonGhost (below) is onto something GDI+ is not using GDI handles, which I assumed it did! I wish there was some documentation out there that verified this.ColinE

6 Answers

2
votes

Does the GDI+ use GDI handles? I'm not sure, though I read somewhere that there is a .NET System.Drawing implementation that relies on bare GDI.

However, maybe you can try to find your leaks with a profiler like AQTime instead.

How are you sure your large app is leaking GDI handles? Is the count in Task Manager large? If so, are you always using GDI+, or also GDI? Does your test app GDI handle count increase if you create your control multiple times?

2
votes

You are not really leaking resources in your sample. Remove this code from your Load event:

for (int i = 0; i < 100; i++)
    {
        pens.Add(new Pen(new SolidBrush(Color.FromArgb(255, i * 2, i * 2, 255 - i * 2))));
    }

Your Paint event handler should look like this:

void TestControl_Paint(object sender, PaintEventArgs e)
{
    for (int i = 0; i < 100; i++)
    {
        e.Graphics.DrawLine(new Pen(new SolidBrush(Color.FromArgb(255, i * 2, i * 2, 255 - i * 2))), 0, i, Width, i);
    }
}

Now you will be leaking in every paint call. Start minimizing/restoring your Form and see GDI objects sky rocket...

Hope this helps.

2
votes

If you want to leak a GDI object from .NET, then just create a GDI object and not release it:

[DllImport("gdi32.dll", EntryPoint="CreatePen", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
private static extern IntPtr CreatePen(int fnStyle, int nWidth, int crColor);

CreatePen(0, 0, 0); //(PS_SOLID, 0=1px wide, 0=black)

Blingo blango, you're leaking GDI pens.

i don't know why you want to create GDI leaks. But your question asked how to create GDI leaks from a WinForm - so there it is.

0
votes

I think the compiler only use one handle.

If I in delphi create a lot of fonts I just take memory
but if I use the WinAPI CreateFont() I take GDI objects.

0
votes

Create two buttons on a form. Inside each button, add the following code. In one button, comment out the Dispose method.

    Form _test = null;
    for (int i = 0; i < 20; i++)
    {
        _test = new Form();
        _test.Visible = false;
        _test.Show();
        _test.Hide();
        _test.Dispose();
    }

The button with the Dispose commented out shows you the leak. The other shows that Dispose causes the User and GDI handles to stay the same.

This is probably the best page I've found that explains it.

0
votes

I think the following blog may have answered this question:

Using GDI Objects the Right Way

The GDI objects that aren't explicitly disposed should be implicitly disposed by their finalizes. (Bob Powell has also mentioned this issue in GDI+ FAQ )

But I doubt if the CLR garbage collector can remove GDI resources so quickly that we can't even see memory usage changes from TaskManager. Maybe current GDI+ implementation doesn't use GDI.

I've tried the following piece of code to generate more GDI objects. But I still couldn't see any changes of the number of GDI handles.

void Form1_Paint(object sender, PaintEventArgs e) 
{
    Random r = new Random();
    while (true)
    {
        for (int i = 0; i < 100; i++)
        {
            e.Graphics.DrawLine(
            new Pen(new SolidBrush(Color.FromArgb(r.Next()))), 0, i, Width, i);
        }
    }
}