Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Office Interop Object Collection Technique – Ghost Excel Instance

4.00/5 (2 votes)
4 Jan 2010CPOL2 min read 16.8K  
Office Interop Object Collection Technique – Ghost Excel Instance

Recently, we wrote an add-in for Microsoft PowerPoint to automate several routine tasks for our business users. While developing this add-in, we required to open a temp workbook in a new Excel application instance to perform several tasks. Once these tasks were completed, the temp workbook was supposed to be closed and the Excel application needed to be quit. However to our amazement, we found that in spite of quitting Excel application by calling Quit() on Excel application object, we found it to be silently running in task manager. It seemed that this process lives in memory forever until the AppDomain on which add-in is loaded is torn.

After this, we researched a bit to see what is really causing the Excel process to live in memory forever. And to answer this and several other basic questions, I wrote a small windows console application as my POC. Below is how the source appears.

C#
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Excel = Microsoft.Office.Interop.Excel; 
using System.Reflection; 
using System.Runtime.InteropServices;
namespace GhostXLInstance 
{ 
  class Program 
  {
        static void Main(string[] args)
        {
            Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
            Excel.Workbook newWorkbook = xlApp.Workbooks.Add(Missing.Value);
            int workbookRCWCounter = Marshal.ReleaseComObject(newWorkbook);
            Console.WriteLine("RCW counter for workbook :: 
		" + workbookRCWCounter.ToString());
            xlApp.Quit();
            int excelRCWCounter = Marshal.ReleaseComObject(xlApp);
            Console.WriteLine("RCW counter for XL application :: 
				" + excelRCWCounter.ToString());
            Console.Read();
        }
    }
}

In the above code, I am creating a new Excel application, adding new workbook in it and then releasing both application and workbook references (observe that we explicitly created only two interop references xlApp and newWorkbook).

After executing the example, you will see two lines in the console telling you the RCW count for each object we released. Viola!!! It is zero which apparently means that all the objects we referenced are released. However, if you observe the task manager now, the Excel instance is still running. So the question still remains the same – What is holding the Excel process?

Well, then I got a fantastic article http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx which explained the phenomena. So here is the phenomenon in simple words. If you closely observe our example, we are actually getting three references. The third reference is implicitly created when we used the application’s Workbooks collection to add new workbook.

C#
Excel.Workbook newWorkbook = xlApp.Workbooks.Add(Missing.Value);

Observe that xlApp.Workbooks actually returns Microsoft.Office.Interop.Excel.Workbooks reference. Wow! So did we get the cause of immutability of Excel process? Yes! So this uninvited third reference lives forever in the memory and causes the Excel process to wait for its garbage collection.

Now since we got the cause, let's modify our previous code to release third reference even.

C#
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Excel = Microsoft.Office.Interop.Excel; 
using System.Reflection; 
using System.Runtime.InteropServices; 
namespace GhostXLInstance 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application(); 
            Excel.Workbooks workbooks = xlApp.Workbooks; 
            Excel.Workbook newWorkbook = workbooks.Add(Missing.Value);            
            int workbookRCWCounter = Marshal.ReleaseComObject(newWorkbook); 
            Console.WriteLine("RCW counter for workbook :: 
				" + workbookRCWCounter.ToString()); 
            int workbooksRCWCounter = Marshal.ReleaseComObject(workbooks); 
            Console.WriteLine("RCW counter for workbooks :: 
				" + workbooksRCWCounter.ToString()); 
            xlApp.Quit(); 
            int excelRCWCounter = Marshal.ReleaseComObject(xlApp); 
            Console.WriteLine("RCW counter for XL application :: 
				" + excelRCWCounter.ToString()); 
            Console.Read(); 
        } 
    } 
}

Finally in modified code, I am collecting the xlApp.Workbooks reference to a local variable and then releasing it. And now executing this code would release the ghost application while console application is waiting at Console.Read().

Conclusion

Well so from our POC it became apparent that the so called collection references (like workbooks, shapes, charts, …) need to be explicitly referenced in local variable and garbage collected to neatly exit Excel process.

Note: In my next posting, I will detail the right way to release Interop references.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)