1

I have a class that populates 1000 ComboBoxes into the UI, the code is

namespace ActiveXMemory
{
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
 public partial class MainWindow : Window
 {
    public MainWindow()
    {
        InitializeComponent();
        PopulateControls();
    }
    public void PopulateControls() {
        var newGrid = new Grid();
        for (var i = 0; i < 1000; i++)
        {
            ComboBox newcombo = new ComboBox();
            newGrid.Children.Add(newcombo);
        }
        this.Content = newGrid;
    }

    public bool OpenWindow(bool isRunning)
    {
        Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
        {
            try
            {
                this.ShowDialog();
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
            }
        }));
        return true;
    }

    public bool CloseWindow()
    {
        Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
        {
            try
            {
                this.Close();
            }
            catch (Exception exp)
            {
                //Console.WriteLine(exp.Message);
            }
        }));

        return true;
    }

    private void Window_Closed(object sender, EventArgs e)
    {
        var grid = Content as Grid;

        var children = grid.Children;

        while (children.Count > 0)
        {
            var child = children[0];
            grid.Children.Remove(child);
            child = null;
        }
        grid = null;
    }

 }
}

I created an ActiveX library for the class to be accessible as ActiveX,

namespace ActiveXLibrary
{

[ComVisible(true)]
[Guid("EF2EAD91-68A8-420D-B5C9-E30A6F510BDE")]
public interface IActiveXLib
{
    [DispId(1)]
    bool Initialize();
    [DispId(2)]
    bool CloseActiveX();
}

[ComVisible(true)]
[Guid("9DACD44F-0237-4F44-BCB9-0E6B729915D6"),
ClassInterface(ClassInterfaceType.None)]
[ProgId("Samp")]
public class ActiveXLib : IActiveXLib
{
    MainWindow form;
    Thread thr;
    public bool Initialize()
    {
        int i = 0;
        try
        {
            ThreadStart exeFunc = new ThreadStart(() =>
            {
                Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Send, new Action(() =>
                {
                    start();
                }));
            });
            thr = new Thread(exeFunc);
            thr.SetApartmentState(ApartmentState.STA);
            thr.Start();

            while (form == null)
            {
                i++;
                //Console.WriteLine("form Null");
                System.Threading.Thread.Sleep(1000);
                if (i > 30)
                    break;
            }

            return true;
        }
        catch (Exception exp)
        {
            Console.WriteLine("[Initialize]" + exp.Message);
            return false;
        }
    }

    public bool CloseActiveX()
    {
        bool success = false;
        try
        {

            success = form.CloseWindow();
            form = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            return success;
        }
        catch (Exception exp)
        {
            Console.WriteLine("[CloseActiveX]" + exp.Message);
            return false;
        }
    }

    private void start()
    {
        try
        {
            Console.WriteLine("start() - new MainWindow()");
            form = new MainWindow();
            Console.WriteLine("OpenWindow()");
            form.OpenWindow(true);
        }
        catch (Exception exp)
        {
            MessageBox.Show(exp.Message + "\nPossible Reasons: " + exp.Source + "\n" + exp.InnerException, "Error");
        }
    }
}

}

Then I created a demo project as

  ActiveXLib activeXRef;
    public MainWindow()
    {
        InitializeComponent();
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef = new ActiveXLib();
        activeXRef.Initialize();

    }

    private void stopButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef.CloseActiveX();
        GC.Collect();
    }

Here the problem is every time I click the start and stop button, the memory keeps on increasing, the memory (17MB) created by the first start of the application did not get released, even though I removed the ComboBoxes and set the grid content null. Even the GC.Collect() has no effect, since it only adds the garbage to a queue. Is there any fix for this.

enter image description here

I also tried flushing the memory as in the link, But even then the memory is still held.(since I am not sure whether the handle of the current process is from demo project/from ActiveXMemory)

Edit

I included the line

 this.Dispatcher.Invoke(DispatcherPriority.Render, GCDelegate);

to the Window_Closed event

Now the memory gets cleared

enter image description here

But one problem I am facing now is, if I try to immediately close (call CloseActiveX()), then this is not happening.(i.e)

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef = new ActiveXLib();
        activeXRef.Initialize();
        activeXRef.CloseActiveX();
        activeXRef = null;
        GC.Collect();

    }

The above code still has the memory locked, I don't understand the difference, it is just another event,Does anyone have any idea on this?

keerthee
  • 812
  • 4
  • 17
  • 39
  • Have you tried to do a Marshal.ReleaseComObject(activeXRef) before creating a new one (if activeXRef is not null of course). – Simon Mourier Apr 26 '16 at 06:22
  • I tried, getting ArgumentException "{"The object's type must be __ComObject or derived from __ComObject.\r\nParameter name: o"}" – keerthee Apr 26 '16 at 06:43
  • Oh yes of course, sorry, I overlooked your code and thought IActiveXLib was not .net. You can analyse your program with WinDbg which is cool to understand what's going on: https://blogs.msdn.microsoft.com/johan/2007/01/11/i-am-getting-outofmemoryexceptions-how-can-i-troubleshoot-this/ or https://blogs.msdn.microsoft.com/tess/2008/02/20/net-debugging-demos-lab-3-memory-review/ – Simon Mourier Apr 26 '16 at 08:24
  • I have tried the memory profilers and saw what still exists in the physical memory, but I don't know how to fix it – keerthee Apr 26 '16 at 09:18

2 Answers2

3

I think I got a repro for this problem although I can't tell what your MainWindow.Open/CloseWindow() methods look like. Using a thread is certainly part of the problem, there is one non-intuitive thing you have to do to prevent leaking internal WPF plumbing objects that have thread affinity.

It is imperative to shutdown the dispatcher for the thread. This normally just happens once in a WPF app when the UI thread terminates. Also very important that the dispatcher does the dispatching, WPF heavily depends on it to ensure that "weak events" are actually weak.

A sample implementation for MainWindow that does not leak:

public class MainWindow : Window {
    public void OpenWindow(bool noidea) {
        this.ShowDialog();

    }
    public bool CloseWindow() {
        this.Dispatcher.Invoke(() => {
            this.Dispatcher.InvokeShutdown();   // <== Important!
        });
        return true;
    }
}

Where ShowDialog() ensures the dispatcher does the dispatching and the InvokeShutdown() ensures that the cleanup occurs. Tested by using a 10 msec DispatcherTimer instead of buttons so this happened at a very high rate. I could remove the GC.Collect() calls and memory usage was stable.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Yeah this actually got it working this.Dispatcher.Invoke(DispatcherPriority.Render, GCDelegate);this.Dispatcher.Invoke(() => { this.Dispatcher.InvokeShutdown(); // <== Important! }); fixed the memory leak – keerthee Apr 27 '16 at 07:03
1

At the moment I've no comment about the ActiveX part (will double check and let you know), but I can suggest something related to the WPF code.

This should make the combo items disposable

public class DispCombo : ComboBox, IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}
public class DispGrid : Grid, IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}

You will populate disposable controls

public void PopulateControls()
{
    var newGrid = new DispGrid();
    for (var i = 0; i < 1000; i++)
    {
        DispCombo newcombo = new DispCombo();
        newGrid.Children.Add(newcombo);
    }
    this.Content = newGrid;
}

So the closing phase would become (disposing instead of setting to null)

private void Window_Closed(object sender, EventArgs e)
{
    var grid = Content as DispGrid;

    var children = grid.Children;

    while (children.Count > 0)
    {
        var child = children[0] as DispCombo;
        grid.Children.Remove(child);
        child.Dispose();
    }
    grid.Dispose();

}

That said, now you can wrap the WPF view in a using clause

public class MainLibrary
    {
        public bool OpenWindow() //found no ref to bool isRunning
        {

            using (var mw = new MainWindow())
            {
                mw.ShowDialog();
                mw.Close();
            }

            return true;
        }

        public bool CloseWindow()
        {
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            return true;
        }
    }

with your garbage collection in the close. These are my memory snapshots after these changes, taken (1) at start, then (2) after the main ShowDialog in the OpenWindow and finally (3) after the CloseWindow

enter image description here

Edit

Finally, if you need to start a Task, calling the Dispatcher, here it is the function

public bool CloseWindow()
        {
            winClose = () =>
            {
                mw.Dispatcher.Invoke(() =>
                   {
                       mw.Close();
                       mw.Dispose();
                   });
            };
            cleanProc = () =>
            {
                mw.Dispatcher.Invoke(() =>
                {
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                    GC.WaitForPendingFinalizers();
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                });
            };
            Task.Run(() =>
           {
               winClose.Invoke();
           }).ContinueWith(x =>

               {
                   cleanProc.Invoke();
               });

            return true;
        }

with

public bool OpenWindow() 
        {
            mw = new MainWindow();
            mw.Show();
            return true;
        }
  • Thanks for your answer.Actually, I need the MainWindow to be closable from the Demo project, but if I show dialog without dispatcher, then close must be done explicitly by an user event. Also the "form" object in the ActiveX library must be available as an instance to make any other changes in the MainWindow – keerthee Apr 27 '16 at 06:08
  • Sure, you're right. That's the reason why I added my edit. Sorry for being unclear about it. I've tried from a wpf button (linked to my library CloseWindow) in another view and I think it's closable. Let me know if I'm still missing something. Thanks again. –  Apr 27 '16 at 06:49
  • Yeah, this works, but ,mw.Show() causes the window to be not accessible for the userevents(like click/keyboard events) – keerthee Apr 27 '16 at 07:04