NOTE: This article was originally published as a post in the .NET and Memory blog (by Andreas Suurkuusk, co-founder of SciTech Software AB).
When investigating memory problems reported by a user of .NET Memory Profiler (in our support forum), a resource leak was discovered. To illustrate the leak, I have created a very simple Windows Forms application, with a form that contains a single button. In the Click handler of the button, a number of TreeViews are created and the Checkboxes are enabled. They are added to the form, just to make sure that they will be fully initialized and then immediately disposed.
private void button1_Click( object sender, EventArgs e )
{
for( int i = 0; i < 100; i++ )
{
using( TreeView treeView = new TreeView() )
{
this.Controls.Add( treeView );
treeView.CheckBoxes = true;
}
}
}
This should not cause any left over instances; neither managed memory instances nor unmanaged resource instances. But when testing this under the profiler, the following results can be seen (after collecting a snapshot before and after a button click).
There are 200 new HBITMAPs and HDCs, and 100 new HIMAGELISTs! Considering that we created 100 TreeViews, it seems very likely that the new resource instances are related to them. Let’s investigate one of the resource further, for example the HBITMAPs. The allocation call stacks of the bitmaps provide the following information:
Thanks to the improved native stacks in .NET Memory Profiler 3.5, you can easily see that the HBITMAP is created as part of an ImageList, when initializing the checkboxes in the native TreeView (TV_InitCheckBoxes). So setting the CheckBoxes property to true, caused the HBITMAP to be created, but why is it not released? At first I suspected that there was actually a resource leak in the native Win32 TreeView implementation, but when checking the documentation, I found this:
Once a tree-view control is created with this style, the style cannot be removed. Instead, you must destroy the control and create a new one in its place. Destroying the tree-view control does not destroy the check box state image list. You must destroy it explicitly. Get the handle to the state image list by sending the tree-view control a TVM_GETIMAGELIST message. Then destroy the image list with ImageList_Destroy .
Apparantly, it’s the responsibility of the user of the native TreeView (e.g. the .NET TreeView control) to release the image list. I find this a bit odd since this image list is created as a side-effect of the internal implementation of the check boxes. Anyway, it seems like the .NET TreeView control fails to release the image list. To fix this leak, the image-list must be explicility released. The ReleaseCheckBoxImageList method below shows an example on how this can be done.
static class TreeViewHelper
{
///
/// Releases the checkbox image list in the
/// specified TreeView.
///
internal static void ReleaseCheckBoxImageList(TreeView treeView)
{
if( treeView.IsHandleCreated )
{
int style = GetWindowLong( treeView.Handle, GWL_STYLE );
if( (style & TVS_CHECKBOXES) != 0 )
{
// Checkboxes were enabled in the
// tree view. Let's destroy the
// image list.
IntPtr hStateImageList = SendMessage(
treeView.Handle,
TVM_GETIMAGELIST,
(IntPtr)TVSIL_STATE,
IntPtr.Zero );
if( hStateImageList != IntPtr.Zero )
{
ImageList_Destroy( hStateImageList );
}
}
}
}
#region Windows interop
const int TV_FIRST = 0x1100;
const int TVM_GETIMAGELIST = (TV_FIRST + 8);
const int TVSIL_STATE = 2;
const int TVS_CHECKBOXES = 0x0100;
const int GWL_STYLE = (-16);
[DllImport( "User32" )]
static extern IntPtr SendMessage(
IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
[DllImport( "User32" )]
static extern int GetWindowLong(
IntPtr hWnd, int nIndex);
[DllImport( "comctl32" )]
[return: MarshalAs( UnmanagedType.Bool )]
static extern bool ImageList_Destroy(IntPtr himl);
#endregion
}
This method should be called when the image list will no longer be needed, for instance when the window handle is destroyed. The FixedTreeView class below shows how this can be implemented:
///
/// A TreeView that properly releases
/// native Win32 resources.
///
class FixedTreeView : TreeView
{
///
/// Overridden to make sure that the native
/// checkbox image list is properly released.
///
protected override void OnHandleDestroyed(EventArgs e)
{
TreeViewHelper.ReleaseCheckBoxImageList( this );
base.OnHandleDestroyed( e );
}
}
By using the FixedTreeView instead of the standard TreeView, the test program no longer has a resource leak (or memory leak).
As you can see, this fixed the resource leak of HBITMAPs, HIMAGELISTs, HDCs, and even the HeapAlloced memory.
An additional bonus is that this fix will also handle the case where the CheckBoxes property is toggled. When the CheckBoxes property is set to false, the image list should also be released. Thanks to the fact that the native TreeView must be recreated whenever the checkbox style is updated, the handle will be destroyed when the CheckBoxes property is changed and the fix will executed ( “Once a tree-view control is created with this style, the style cannot be removed. Instead, you must destroy the control and create a new one in its place” ).
The code in the forum post mentioned in the beginning didn’t handle this correctly, since it used the CheckBoxes property to decide if the image list should be released. This property don’t reflect the whether checkboxes were included in the control being destroyed. Therefore the TVS_CHECKBOXES style is checked instead, as that will handle setting CheckBoxes to false as well.
Download .NET Memory Profiler to see how it can help you find memory leaks and optimize memory usage in your application.
Download Free Trial