I recently downloaded a copy of a new tool from red-gage
called Ant Memory Profiler. Ant Memory Profiler is an application that allows
you to do memory usage analysis on dot net apps to uncover how your app
consumes memory.
During the course of trying out this tool we uncovered an
interesting memory leak in the dot net framework ToolStrip
control. Our symptom of this was that one of our application forms was being
held in memory long after it should have been garbage collected. After a quick
tour through google I found a some
references on why this is occurring and how to ensure things get cleaned up
properly.
Here’s how we add the cleanup code to the close event of the
form…
Protected Overrides Sub
OnClosed(ByVal e As
System.EventArgs)
MyBase.OnClosed(e)
'note, this
call is still required as it cleans up one set of extra handlers
reportViewer.Toolbar.Visible = False
'this is the
really ugly stuff though, let our cleanup class take care of things
Dim gc2
As New
ToolStripGarbageCollector
gc2.RemoveHandlers(reportViewer)
End Sub
And heres the
meat behind the cleanup class. Don’t ask what this does, it’s pretty
much a copy and paste I found from some newsgroups.
Public Class ToolStripGarbageCollector
Private Const EVENTHANDLER_ON_USER_PREFERENCE_CHANGED As String = "OnUserPreferenceChanged"
Private Const LIST_HANDLERS As
String = "_handlers"
Private Const ON_USER_PREFERENCE_CHANGED_EVENT As String = "OnUserPreferenceChangedEvent"
Private Const SYSTEM_EVENT_INVOKE_INFO As String = "SystemEventInvokeInfo"
Private Const TARGET_DELEGATE As
String = "_delegate"
Public Sub RemoveHandlers(ByVal
ctrl As Control)
NavigateControls(ctrl)
End Sub
Public Sub NavigateControls(ByVal
ctrl As Control)
Console.WriteLine(ctrl.Name & "-" & ctrl.GetType.FullName)
If
ctrl.GetType.FullName = "System.Windows.Forms.ToolStripTextBox+ToolStripTextBoxControl"
Then
RemoveUpcHandler(ctrl)
ElseIf
ctrl.GetType.FullName = "System.Windows.Forms.ToolStrip"
Then
RemoveUpcHandler(ctrl)
End If
For Each c As Control In ctrl.Controls
NavigateControls(c)
Next
End Sub
Private Sub RemoveUpcHandler(ByVal
ctrl As Control)
'create the
delegate to OnUserPreferenceChanged
Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler),
ctrl, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)
Dim
handlers As Object
= GetType(Microsoft.Win32.SystemEvents).GetField("_handlers",
Reflection.BindingFlags.NonPublic Or
Reflection.BindingFlags.Static).GetValue(Nothing)
Dim
upcHandler As Object
= GetType(Microsoft.Win32.SystemEvents).GetField(ON_USER_PREFERENCE_CHANGED_EVENT,
Reflection.BindingFlags.NonPublic Or
Reflection.BindingFlags.Static).GetValue(Nothing)
'get a
SystemEventInvokeInfo type
Dim
systemEventInvokeInfo As Object = GetType(Microsoft.Win32.SystemEvents).GetNestedType(SYSTEM_EVENT_INVOKE_INFO,
Reflection.BindingFlags.NonPublic Or
Reflection.BindingFlags.Instance)
'get the
SystemEventInvokeInfo list for the UserPreferenceChangedEvent
Dim
upcHandlerList As IList = CType(CType(handlers,
IDictionary).Item(upcHandler), IList)
'initialize a
target count
Dim
targetCount As Integer
= 0
Dim i As Integer = 0
'loop
While i
< upcHandlerList.Count
systemEventInvokeInfo =
upcHandlerList(i)
'get the
SystemEventInvokeInfo._delegate field
Dim
target As [Delegate] = CType(systemEventInvokeInfo.GetType().GetField(TARGET_DELEGATE,
Reflection.BindingFlags.NonPublic Or
Reflection.BindingFlags.Instance).GetValue(systemEventInvokeInfo), [Delegate])
'eval
If
target.Target Is d.Target Then
'increment
on positive ID
targetCount += 1
End
If
i += 1
End While
'remove the
handlers
For i =
1 To targetCount
RemoveHandler
Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d,
Microsoft.Win32.UserPreferenceChangedEventHandler)
Next
End Sub
End Class