1

We are currently reworking a WindowsForms application in WPF. The software is quite large and it will take years to finish it, so we have a hybrid system that displays pure WPF for the new panels and Hosted WindowsForms elements in WindowsFormsHosts controls. We use Syncfusion's WPF Docking Manager to show these pages in tabs (not sure that this info is relevant). We spent quite a lot of time to track down memory leaks (using JetBrains' DotMemory), but we run out of memory after opening and closing nearly 100 pages containing WindowsFormsHosts.

This memory leak is quite strange, as you can see in the memory profiling, it seems that the problem lies in the unmanaged memory.

DotMemory profiling

The WindowsFormsHosts seem to be correctly disposed as well as the Child content. As suggested here WPF WindowsFormsHost memory leak, we wrap the WindowsFormsHosts in a grid that we clear when we want to dispose it:

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();
}

and

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

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

We suspect that the Hosting causes the leak because in DotMemory, in memory allocation we can see this:

memory allocation

Is there any known issue with the WindowsFormsHosts that could explain this? Or a way for us to isolate the source of the problem?

Edit : Here is the code that adds the Grid and 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;
}
  • Does the program reload that grid without unsubscribing the events? [Why and How to avoid Event Handler memory leaks?](https://stackoverflow.com/questions/4526829/why-and-how-to-avoid-event-handler-memory-leaks) – Cleptus Feb 15 '23 at 16:47
  • I do not know anything about known issues of disposing WindowsFormsHosts, but I am pretty sure there is no issue. I guess the issue has to be somewhere in your code. Two things caught my eye when watching on the code. 1: Is it possible that your grid has more than one children? If so, the dispose method of the children gets not executed. 2: You do the null pointer check at `kfh?.Dispose()`, but one line above you do not check if `kfh` is null. Can it be null? If so, the Dispose method of your base class gets not executed due to a null reference exception. – Sebastian S. Feb 15 '23 at 16:50
  • You're disposing of the Child Control, but is the Child Control disposing unmanaged resources when it's disposed? Does it handle unmanaged resources (Bitmaps, most of all, but also Graphics objects, Pens, Brushes, Fonts etc.)? If so, the Dispose method of the Child Control must take care of these -- You should check `if (disposing) { // the rest }` and call `base.Dispose(disposing)` – Jimi Feb 15 '23 at 17:19
  • Knowing WinForms there will be a ton of code that will cause the memory leaks. Can you confirm that this wasn't an issue before? WinForms is a free for all, coding cowboys heaven. – XAMlMAX Feb 15 '23 at 19:33
  • @Cleptus sadly no, we only listen one event when we display the WFH and that's it. We then unsubscribe in the dispose method. – Tom RAVANEL Feb 16 '23 at 10:30
  • @Sebastian S. The grid has only one child that we add at the beginning. I will update the post with the code. The null check is unnecessary since it's checked by the condition above. The code was slightly different before, I'll will remove it. – Tom RAVANEL Feb 16 '23 at 10:30
  • @Jimi that's an interesting hypothesis. I read the code and it seems that it gets called. But as XAMlMAX pointed out, I tested with same protocol and it wasn't an issue before (I'll try it anyway). I might be wrong but it seems that the WindowsFormHost uses bitmap to do the conversion maybe something is wrong there? – Tom RAVANEL Feb 16 '23 at 10:31

1 Answers1

1

I finally figured it out: After a bit more digging, I found out that the WindowsFormsHosts are kept alive. DotMemory gives me two reasons for that.

  • the first one: System.Windows.Interop.HwndSourceKeyboardInputSite. For a reason that I can't explain, there is still a reference to the WindowsFormsHost in the ChildKeyboardInputSinks in the HwndSource. If someone knows why, I'd be interested in hearing the reason. I don't know if there is a better way to get rid of this reference, but here's the code I wrote:
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 });
}

I execute this method just before disposing the WindowsFormsHost.

  • The second reason was RootSourceRelationManager. This one gave me a headache. So I did a "Full" dotmemory profiling (before it was a sampled one). The result was strange, the memory leak disappeared. The full profiling needs to be executed from Dotmemory and not from Visual Studio. That was the only difference. After some research I found out that Microsoft.Visual.Design Tools.WpfTap.wpf was involved. So it seems (I can't be sure) that executing from visual studio causes a memory leak (which is not that serious but a good thing to know).

In the end, after releasing a new test version of the software with the clean method, I no longer have a memory leak.

  • Hey Tom - I am currently digging in to a very similar leak involving a Microsoft WebView2 component hosted in a WPF window. If I find anything more that sheds light on this problem I will post here. Just out of curiosity it would be nice to know what version of WPF/.Net you're working on here? – GrahamMc Mar 01 '23 at 19:53
  • Hi @GrahamMc, we are using the version 4.6.2 of the .Net Framework. – Tom RAVANEL Mar 02 '23 at 11:12
  • Thank you. I'm on .Net 7.0.200 if that is of use to you to know; I can also state that like you, I am experiencing "extra" references when running within Visual Studio. I haven't cracked it yet - your answer above reduced one of the paths but I still have another similar path remaining that I am trying to understand. Thank you for the help! – GrahamMc Mar 02 '23 at 12:30