# Monday, April 05, 2010

ToolStripTextBox Memory Leaks

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

 

#    Comments [0] |