Memory leak with custom FrameWorkElementAutomationPeer with IExpandCollapseProvider in C#

815 views Asked by At

We use AutomationUIClient console application to test our WPF application.

We set up in our WPF application a custom class TreeItemAutomationPeer with ISelectionProvider ad IExpandCollapseProvider

If this object is use by automation application console, the Owner (TreeItem in my case) is kept so it leak...

We add the last method GetChildrenCore() to prevent memory leaks from Children.

    public class TreeItemAutomationPeer : FrameworkElementAutomationPeer, ISelectionItemProvider, IExpandCollapseProvider
{
    private readonly TreeItem _treeItem;

    public TreeItemAutomationPeer(TreeItem treeItem)
        : base(treeItem)
    {
        _treeItem = treeItem;
    }

    public override object GetPattern(PatternInterface patternInterface)
    {
        if (patternInterface == PatternInterface.SelectionItem ||
            patternInterface == PatternInterface.ExpandCollapse)
            return this;
        return base.GetPattern(patternInterface);
    }

    protected override string GetClassNameCore()
    {
        return "TreeItem";
    }

    protected override AutomationControlType GetAutomationControlTypeCore()
    {
        //return AutomationControlType.Tree;
        return AutomationControlType.Custom;
    }

    #region ISelectionItemProvider
    public IRawElementProviderSimple SelectionContainer
    {
        get { return _treeItem.SelectionContainer; }
    }
    public bool IsSelected { get { return _treeItem.Item.IsSelected; } }
    public void AddToSelection()
    {
        _treeItem.Item.IsSelected = true;
        ItemHelper.SelectItem(_treeItem, _treeItem.Item);

    }
    public void RemoveFromSelection()
    {
        _treeItem.Item.IsSelected = false;
    }
    public void Select()
    {
        if (_treeItem.Item.IsSelected)
            RemoveFromSelection();
        else AddToSelection();
    }
    #endregion

    #region IExpandCollapseProvider

    public ExpandCollapseState ExpandCollapseState
    {
        get
        {
            return _treeItem.Item.IsExpanded
                ? ExpandCollapseState.Expanded
                : ExpandCollapseState.Collapsed;
        }
    }

    public void Expand()
    {
        _treeItem.Item.IsExpanded = true;
    }

    public void Collapse()
    {
        _treeItem.Item.IsExpanded = false;
    }

    #endregion
    protected override List<AutomationPeer> GetChildrenCore()
    {
        return null;
    }

}

The TreeItem class implement IRawElementProviderSimple

        #region Automation

    private TreeItemAutomationPeer _itemAutomationPeer;
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        _itemAutomationPeer = new TreeItemAutomationPeer(this);
        return _itemAutomationPeer;
    }

    public IRawElementProviderSimple SelectionContainer
    {
        get { return _container; }
    }

    #endregion

    #region IRawElementProviderSimple

    protected IntPtr GetWindowHandle() { return IntPtr.Zero; }
    protected string GetName() { return Name; }
    protected void AddAutomationProperty(int propertyId, object value) { }
    public object GetPatternProvider(int patternId) { return null; }
    public object GetPropertyValue(int propertyId)
    {
        return propertyId == AutomationElementIdentifiers.NameProperty.Id ? GetName() : null;
    }
    public IRawElementProviderSimple HostRawElementProvider { get { return null; } }
    public ProviderOptions ProviderOptions
    {
        get { return ProviderOptions.ServerSideProvider; }
    }

    #endregion

Here is the leak from DotMemory :

Leak in DotMemory

When I took the snapshot, console application is still attached to the WPF application.

How can I release the TreeItemAutomationPeer's Owner from ExpandCollapseProviderWrapper to prevent the leak ?

In the console application, we get AutomationElement object. Is there a list of all the AutomationElement we used and a way to release them ?

Thanks a lot :)

Whiletrue

EDIT :

According to the dotmemory documentation ( https://www.jetbrains.com/help/dotmemory/Analyzing_GC_Roots.html ) RefCounted handle is :

The root prevents garbage collection if the reference count of the object is a certain value. If an object is passed to a COM library using COM Interop, CLR creates a RefCounted handle to this object. This root is needed as COM is unable to perform garbage collection. Instead, it uses reference counting. If the object is no longer needed, COM sets the count to 0. This means that RefCounted handle is no longer a root and the object can be collected. Thus, if you see RefCounted handle, then, probably, the object is passed as an argument to unmanaged code.

1

There are 1 answers

0
Whiletrue On BEST ANSWER

I found a workaround ! :)

In the console application, i use System.Diagnostics.Process.Start() to start my application.

As the TreeItemAutomationPeer was held by COM, i tried to "detatch" from process, call the GC and reattach to the process.

 var processId = process.Id;
        process = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        process = System.Diagnostics.Process.GetProcessById(processId);

After this no more Leak in my dotMemory snapshot \o/