I did a quick check of this memory leak. Using our profiler it was easy to
see that the Form is being referenced by a MenuItem that has been added to
the static Hashtable "allCreatedMenuItems". By disassembling the MenuItem
class it's also quite easy to see what's causing the MenuItem to be added to
the hashtable. When the menuitem is shown AND when it's updated, a
MENUITEMINFO class is created. When creating the MENUITEMINFO a uniqueId for
the MenuItem is assigned (allowing the MenuItem to be identified when
receiving Win32 messages). The MenuItem is then added to the
"allCreatedMenuItems" hashtable. In the Dispose function the MenuItem is
removed from this hashtable. The problem is that the MenuItem is added to
the hashtable, with a new uniqueId, each time the MenuItem is updated.
The following code can be found in MenuItem.CreateMenuItemInfo:
- Code: Select all
<snip>
this.uniqueId = ++MenuItem.createdMenuItemsCounter;
MenuItem.allCreatedMenuItems.Add( this.uniqueId, this );
</snip>
If the above code was replaced with the following, there would be no memory
leak:
- Code: Select all
<snip>
if( uniqueId == -1 ) // uniqueId is assigned to -1 in the constructor.
{
this.uniqueId = ++MenuItem.createdMenuItemsCounter;
MenuItem.allCreatedMenuItems.Add( this.uniqueId, this );
}
</snip>
Another sideeffect of this bug is that the allCreatedMenuItems hashtable
grows each time a MenuItem is changed (e.g. changing the Enable property),
and never shrinks (except for one entry being removed in MenuItem.Dispose).
The problem with the growing hashtable has actually been reported as a bug
previously in this newsgroup.
The only workaround I can come to think of is to use reflection to explictly
remove all added entries from the hashtable.
Something like:
- Code: Select all
static Hashtable allCreatedMenuItems;
void DisposeMenu( Menu menu )
{
if( allCreatedMenuItems == null )
{
Type miType = typeof( MenuItem );
FieldInfo htField = miType.GetField( "allCreatedMenuItems",
BindingFlags.Static | BindingFlags.NonPublic );
allCreatedMenuItems = (Hashtable)htField.GetValue( null );
}
// Find out keys to remove.
ArrayList keysToRemove = new ArrayList();
foreach( DictionaryEntry de in allCreatedMenuItems )
{
if( de.Value == menu )
keysToRemove.Add( de.Key );
}
// And remove them.
foreach( object key in keysToRemove )
{
allCreatedMenuItems.Remove( key );
}
// Dispose children
foreach( MenuItem mi in menu.MenuItems )
{
DisposeMenu( mi );
}
}
Add the above code to the Dispose method of the Form containing the Menu
(the Menu and all its MenuItems should already have been Disposed, which is
done automatically by the code generated by the forms designer):
- Code: Select all
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
DisposeMenu( contextMenu1 );
}
base.Dispose( disposing );
}
I hope this solves your problems and that this message answers your question
in the e-mail you sent to us.