Why does the last MDI child form that was closed not get garbage collected?
c#, forms, mdichild, memory-leaks, winforms
Solution
Technically, because that `Form` is the "FormerlyActiveMdiChild". This looks like a bug. Fortunately, not a very serious one.
The ability to troubleshoot uncollected objects is a good skill to have. The windbg debugger from Microsoft that comes with the Debugging Tools for Windows (http://www.microsoft.com/whdc/devtools/debugging/default.mspx) is great for this purpose. In the walkthrough below, note that I have removed a lot of the output from windbg that is not pertinent.
- Instead of creating the MDI child instance of type `Form`, subclass it as `TestChildForm` to make it easy to identify.
- Start the executable and attach windbg. Load the .NET extensions with `!loadby sos mscorwks`.
In windbg, run `!dumpheap -type TestChildForm`.
Address MT Size
01e2e960 001c650c 320
Next, run `!gcroot 01e2e960`.
ESP:3de7fc:Root:01e29a78(System.EventHandler)->
01e26504(WindowsFormsApplication1.Form1)->
01e269b8(System.Windows.Forms.PropertyStore)->
01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
Next, run `!dumparray -details 01e2ef04` and search the output for `01e2e960`.
MT Field Offset Type VT Attr Value Name
6797ea24 40032a3 10 System.Int16 1 instance 56 Key
6797ea24 40032a4 12 System.Int16 1 instance 1 Mask
6798061c 40032a5 0 System.Object 0 instance 01e2e960 Value1
Finally, I ran `!name2ee System.Windows.Forms.dll System.Windows.Forms.Form` followed by `!dumpclass 6604cb84` (as determined by `!name2ee`) and looked for 56.
MT Field Offset Type VT Attr Value Name
67982c4c 4001e80 fd8 System.Int32 1 static 56 PropFormerlyActiveMdiChild
If you would rather use the Visual Studio debugger instead of windbg, you must first enable Properties, Debug, Enable unmanaged code debugging. Substitute `.load sos` for `.loadby sos mscorwks`.
Problem
We've had problems with memory leaks in our application. I've managed to replicate one of the problems with the following simple example: Replication setup 1) Create the following helper class which will be used to track object creation/destruction. ``` public class TestObject { public static int Count { get; set; } public TestObject() { Count++; } ~TestObject() { Count--; } } ``` 2) Create an MDI form with three buttons, the first button will create a new MDI child as follows: ``` private void ctlOpenMDI_Click(object sender, EventArgs e) { Form newForm = new Form(); newForm.MdiParent = this; newForm.Tag = new TestObject(); newForm.Show(); } ``` The second button will be used do the same, but with a non-MDI child form: ``` private void ctlOpenNonMDIForm_Click(object sender, EventArgs e) { Form newForm = new Form(); newForm.Tag = new TestObject(); newForm.Show(); } ``` The third button will be used to garbage collect and then display how many TestObject instances are live: ``` private void ctlCount_Click(object sender, EventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); MessageBox.Show("Count: " + TestObject.Count); } ``` Replication steps 1) Click Open MDI form button, then close the MDI form, then click the count button. It will return Count: 1. The MDI child form and the object it references was not garbage collected - something must still have a reference to it. Also: Click open MDI form three times, close all 3 forms, then click the count button. It will return Count: 1. It seems as though the last closed MDI child form is not garbage collected. Counter-cases: 1) Click Open non-MDI form, close it. Then click the count button. It will return Count: 0, the form and object have been garbage collected. Workaround I can workaround this problem by doing this: ``` Form form = new Form(); form.MdiParent = this; form.Show(); form.Close(); ``` Before the garbage collection. This makes this dummy form the last closed MDI child form so that the other ones can be garbage collected - but why should I have to do this? What is going on? Also it's a bit ugly as you will get a flicker of the form opening and closing, and it seems pretty hacky too.