28

I have been having difficulties understanding which exact objects from the System.Drawing namespace actually contribute to the system total GDI object count. For instance, do Matrix objects count? GraphicsPath? Pen?

In order to test this, I ran the following code on Form initialization.

public partial class Form1 : Form
{
    Timer timer = new Timer();
    List<Pen> pens = new List<Pen>();

    public Form1()
    {
        InitializeComponent();

        var r = new Random();
        for (int i = 0; i < 1000; i++)
        {
            var p = new Pen(
                Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)),
                (float)r.NextDouble() * 10);
            pens.Add(p);
        }

        timer.Interval = 30;
        timer.Tick += timer_Tick;
        timer.Start();
    }

    void timer_Tick(object sender, EventArgs e)
    {
        panel1.Invalidate();
    }

    private void panel1_Paint(object sender, PaintEventArgs e)
    {
        for (int i = 0; i < pens.Count; i++)
        {
            e.Graphics.DrawLine(pens[i], (int)(20 + i * 0.1), 20, (int)(50 + i * 0.1), 50);
        }
    }
}

I was surprised to find that my application GDI object count was 32 (measured with Task Manager > Details > GDI objects column). This was true even if I kept the list around and forced the Paint event to draw individual lines with each of the generated pens. I even tried creating random colors to ensure that nothing got reused and still the GDI count kept steady at 32.

There are a lot of posts worrying about caching and reusing Pen instances and other objects, but it is not clear to me whether all System.Drawing objects contribute to this count.

Maybe modern GDI+ implementations are actually lazy and delay allocation only to when you are actually drawing something?

Update: GSerg pointed out that the MSDN official documentation says

When you use GDI+, you don't have to be as concerned with handles and device contexts as you do when you use GDI.

It seems that GDI+ abstracts over GDI handle and avoids using them as much as possible. This is also consistent with reports elsewhere that seem to indicate that GDI+ only actually creates a GDI handle when it absolutely has to. For example, a Bitmap is only backed by a handle if you call methods that require a handle to exist, e.g. GetHBitmap().

It seems that, at least in modern GDI+, Pen, Brush and many other System.Drawing objects do not actually contribute to the total GDI object count. Of course, in C# they still leak unmanaged memory if they are not disposed, since they are backed by unmanaged GDI+ objects, but native memory is not such a harsh mistress as GDI memory.

If this current interpretation is not contested, I will move it to an answer over the next few days.

Fred
  • 2,965
  • 4
  • 29
  • 51
glopes
  • 3,198
  • 3
  • 21
  • 25
  • 2
    I just duplicated your test, but GDIView is showing 0 GDI `Pen` handles (33 total GDI objects). I'm just going to chalk this up to GDI+ working differently. I'm testing on Windows 7 BTW. https://imgur.com/a/QC1CGOb – Bradley Uffner Apr 20 '18 at 12:01
  • @BradleyUffner that is impressive, and good to know reproducible on Windows 7. I'm targeting .NET 4.5+ so I don't really care about earlier OSes than that. I'm just baffled that a lot of the old advice against caching/reusing instances seems to not apply anymore. – glopes Apr 20 '18 at 12:04
  • 3
    If there are additional important information that needs to go into the question, you should copy from the chat and insert as an edit. – Samuel Liew Apr 20 '18 at 13:45

1 Answers1

-1

The following objects cause a GDI handle to be created:

  • Bitmap
  • Brush
  • DC
  • Enhanced Metafile
  • Enhanced Metafile DC
  • Font
  • Memory DC
  • Metafile
  • Metafile DC
  • Palette
  • Pen
  • Extended Pen
  • Region

This list was drawn up by using the information from here: https://docs.microsoft.com/en-gb/windows/desktop/SysInfo/gdi-objects

Cronan
  • 153
  • 1
  • 9
  • 4
    This is extremely misleading. GDI+ (aka System.Drawing) is a fundamental replacement for GDI. They kept the object model the same, helps programmers to port their code, but the implementation is radically different. And has been changing across Windows versions, it uses less of the legacy GDI objects to get the job done so doesn't drive up the Task Manager diagnostic like it used to. The nice thing you can do with an api, it only has to look the same and you're not stuck with the implementation problems of version n-9. – Hans Passant May 30 '19 at 23:03
  • @HansPassant except for hardware acceleration. When you use GDI (not GDI+) on an OS besides Windows Vista you know what will be accelerated and what won’t be. With GDI+/`System.Drawing` there is no way for me to be confident that any drawing besides a bitmap-blit will be done within a single 60Hz (16ms) frame (well, insofar as *all* GDI 2D operations besides blitting were hobbled in Windows 7+. I’m still angry about that.) it’s just a tad concerning as 140Hz and even 280Hz monitors are gaining popularity but GDI is stuck in 1993. – Dai Mar 24 '20 at 11:34