Memory leak - Forms with Owners

Z

zobie

I have created a project that has two forms. Form1 has a button which
runs the following code:

private void button1_Click(object sender, System.EventArgs e)
{
Form2 f = new Form2();
f.Owner = this;
f.Show();
f.Close();
this.RemoveOwnedForm(f);
f.Dispose();
f = null;
}

Using .NET Memory Profiler (http://www.scitech.se/memprofiler) I am able
to see that Form2 is not disposed. This seems to be a huge issue. Has
anyone experienced this problem? Is there a workaround? Have I done
something incorrectly?

If I remove the line where the Owner is set, there is no problem with
Form2 being Disposed.

Thanks,
Nate
 
A

Andreas Suurkuusk

Hi,

You're right. There's a memory leaks when removing owned forms, however it
will in many cases not keep more than one Form referenced. In your example,
if you click the button repeatedly, you still only have one live instance of
Form2.

The error is the way the form is removed in RemoveOwnedForm:

The following code is used to remove the form (indexForm is the index of the
form to remove):

Array.Copy( formArray, indexForm+1, formArray, indexForm, nForms -
indexForm-1);

What's missing is code to null the leftover instance at the end of the
array:

formArray[nForms-1] = null;

How bad the memory leak is depends on how many child forms a form is the
owner of, and the order they are removed from the owner. The code below will
cause 10 Form2 instances to become wrongly referenced by the owner (if you
reverse the Close loop, only one instance will be leftover):

private void button1_Click(object sender, System.EventArgs e)
{
Form2[] forms = new Form2[10];
for( int i=0; i < forms.Length; i++ )
{
forms = new Form2();
forms.Owner = this;
forms.Show();
}

for( int i=forms.Length; --i >= 0; )
{
forms.Close();
}
}

Note that calling Close() on the form will Dispose it, and it will be
automatically removed from the owner. There's no need to set the Form2
references to null, since the variable will go out of scope when the method
returns.

I have not checked whether this leak still exists under Whidbey, but
hopefully
it has been fixed.

Best regards,

Andreas Suurkuusk
SciTech Software AB
 
Z

zobie

Thanks for your help... this is extremely helpful information.

- Nate


Andreas said:
Hi,

You're right. There's a memory leaks when removing owned forms, however it
will in many cases not keep more than one Form referenced. In your example,
if you click the button repeatedly, you still only have one live instance of
Form2.

The error is the way the form is removed in RemoveOwnedForm:

The following code is used to remove the form (indexForm is the index of the
form to remove):

Array.Copy( formArray, indexForm+1, formArray, indexForm, nForms -
indexForm-1);

What's missing is code to null the leftover instance at the end of the
array:

formArray[nForms-1] = null;

How bad the memory leak is depends on how many child forms a form is the
owner of, and the order they are removed from the owner. The code below will
cause 10 Form2 instances to become wrongly referenced by the owner (if you
reverse the Close loop, only one instance will be leftover):

private void button1_Click(object sender, System.EventArgs e)
{
Form2[] forms = new Form2[10];
for( int i=0; i < forms.Length; i++ )
{
forms = new Form2();
forms.Owner = this;
forms.Show();
}

for( int i=forms.Length; --i >= 0; )
{
forms.Close();
}
}

Note that calling Close() on the form will Dispose it, and it will be
automatically removed from the owner. There's no need to set the Form2
references to null, since the variable will go out of scope when the method
returns.

I have not checked whether this leak still exists under Whidbey, but
hopefully
it has been fixed.

Best regards,

Andreas Suurkuusk
SciTech Software AB



I have created a project that has two forms. Form1 has a button which runs
the following code:

private void button1_Click(object sender, System.EventArgs e)
{
Form2 f = new Form2();
f.Owner = this;
f.Show();
f.Close();
this.RemoveOwnedForm(f);
f.Dispose();
f = null;
}

Using .NET Memory Profiler (http://www.scitech.se/memprofiler) I am able
to see that Form2 is not disposed. This seems to be a huge issue. Has
anyone experienced this problem? Is there a workaround? Have I done
something incorrectly?

If I remove the line where the Owner is set, there is no problem with
Form2 being Disposed.

Thanks,
Nate
 
G

Guest

Andreas,

Thanks for the specifics. We noticed the same problem after we began using
your excellent SciTech .NET Memory Profiler.

Attached is our shop's workaround. It relies on reflection and internal
implementation details, so it's really gross. Unfortunately it was necessary,
as forms (and the objects they referenced) not being collected was a serious
issue.

Nothing all that clever is being done; we just manually call FixOwnedForms
whenever an owned form is closed.

--

Option Strict On
Option Explicit On

Imports System.Reflection
Imports System.Windows.Forms

Public Class FormHelper

Public Shared Sub FixOwnedForms(ByVal frm As Form)
If frm Is Nothing Then Throw New ArgumentNullException

Dim iForm As Integer

Dim afrmInternal As Form()
Dim afrmClaimed As Form()

afrmClaimed = frm.OwnedForms
afrmInternal = GetInternalOwnedForms(frm)

For iForm = 0 To afrmInternal.Length - 1
If Array.IndexOf(afrmClaimed, afrmInternal(iForm)) >= 0 Then
' everything is fine
Else
afrmInternal(iForm) = Nothing
End If
Next
End Sub

Public Shared Function GetInternalOwnedForms(ByVal frm As Form) As Form()
Dim typ As Type
Dim pi As PropertyInfo
Dim mi As MethodInfo
Dim fi As FieldInfo

Dim lOwnedFormsConstant As Integer
Dim objProperties As Object
Dim objForms As Object

Dim afrm As Form()

If frm Is Nothing Then Throw New ArgumentNullException

Try
typ = GetType(Form)
fi = typ.GetField("PropOwnedForms", _
BindingFlags.Static Or BindingFlags.NonPublic)

lOwnedFormsConstant = DirectCast(fi.GetValue(Nothing), Integer)

typ = GetType(Control)
pi = typ.GetProperty("Properties", _
BindingFlags.Instance Or BindingFlags.NonPublic)

objProperties = pi.GetValue(frm, Nothing)

typ = objProperties.GetType
mi = typ.GetMethod("GetObject", _
BindingFlags.Instance Or BindingFlags.Public, _
Nothing, _
New Type() {GetType(Integer)}, _
Nothing)

objForms = mi.Invoke(objProperties, New Object()
{lOwnedFormsConstant})

afrm = CType(objForms, Form())

Finally
If afrm Is Nothing Then afrm = New Form() {}
End Try

Return afrm
End Function

End Class

--

Cheers,

Adam Russell
ZS Associates
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top