winforms 释放WindowsFormsHost时发生未管理的内存泄漏

vvppvyoh  于 2023-02-19  发布在  Windows
关注(0)|答案(1)|浏览(346)

我们目前正在用WPF重新开发一个WindowsForms应用程序。这个软件相当大,需要几年的时间才能完成,因此我们有一个混合系统,它为新面板显示纯WPF,并在WindowsFormsHosts控件中显示托管WindowsForms元素。(不确定此信息是否相关)。我们花了相当多的时间来跟踪内存泄漏(使用JetBrains的DotMemory),但在打开和关闭了近100个包含WindowsFormsHosts的页面后,我们耗尽了内存。
这种内存泄漏相当奇怪,正如您在内存分析中所看到的,问题似乎出在非托管内存中。
DotMemory profiling
WindowsFormsHosts和子内容似乎都已正确处置。如此处WPF WindowsFormsHost memory leak所建议的,我们将WindowsFormsHosts Package 在网格中,在需要处置时清除该网格:

public override void Dispose()
{
    if (this.Content is Grid grid && grid.Children.Count == 1)
    {
        if (grid.Children[0] is KWFHost wfh)
        {
            wfh.Child.SizeChanged -= ControlSizeChanged;
            wfh.Dispose();
        }

        grid.Children.Clear();
    }

    base.Dispose();
}

以及

public class KWFHost : WindowsFormsHost
{
    protected override void Dispose(bool disposing)
    {
        if (this.Child is IDisposable disposable)
        {
            disposable.Dispose();
        }

        this.Child = null;
            
        base.Dispose(true);
    }
}

我们怀疑是托管导致了泄漏,因为在DotMemory中,在内存分配中我们可以看到:
memory allocation
WindowsFormsHosts是否有任何已知的问题可以解释这一点?或者我们可以找到一种方法来隔离问题的根源?
编辑:下面是添加网格和WindowsFormHost的代码:

public void SetContent(System.Windows.Forms.Control control)
{
    var host = new KWFHost();
    host.Child = control;
    control.SizeChanged += ControlSizeChanged;

    var grid = new Grid();
    grid.Children.Add(host);

    this.Content = grid;
}
vojdkbi0

vojdkbi01#

我终于想通了:经过进一步的挖掘,我发现WindowsFormsHosts是保持活动的。DotMemory给了我两个原因。

  • 第一个:System.Windows.Interop.HwndSourceKeyboardInputSite。由于我无法解释的原因,HwndSource的ChildKeyboardInputSinks中仍然存在对WindowsFormsHost的引用。如果有人知道原因,我会很想听听原因。我不知道是否有更好的方法来消除此引用,但以下是我编写的代码:
private void CleanHwnd(KWFHost wfh) //my implementation of WindowsFormsHost
{
        HwndSource hwndSource = PresentationSource.FromVisual(wfh) as HwndSource;

        //here I isolate the right element to remove based on the information I have
        IKeyboardInputSink elementToRemove = hwndSource.ChildKeyboardInputSinks.FirstOrDefault(c => c is KWFHost h && h.Parent is Grid g && g.Parent is KWindowsFormsHost fh && Equals(fh.TabName, TabName));

        //The method CriticalUnregisterKeyboardInputSink takes care of removing the reference but as it's protected I use reflection to get it.
        var mi = typeof(HwndSource).GetMethod("CriticalUnregisterKeyboardInputSink",
            BindingFlags.NonPublic | BindingFlags.Instance);

        //here I remove the reference
        mi.Invoke(hwndSource, new object[] { elementToRemove.KeyboardInputSite });
}

我在释放WindowsFormsHost之前执行此方法。

  • 第二个原因是RootSourceRelationManager。这个让我头疼。所以我做了一个“完整”的点内存分析(之前是抽样),结果很奇怪,内存泄漏消失了。完整的分析需要从Dotmemory而不是Visual Studio执行。这是唯一的区别。经过一些研究,我发现涉及到Microsoft.Visual.Design Tools.WpfTap.wpf。所以看起来(我不能肯定)从visual studio执行会导致内存泄漏(这并不严重,但知道这一点是件好事)。

最后,在发布了使用clean方法的软件的新测试版本后,我不再有内存泄漏。

相关问题