Friday, March 23, 2012

Properly disposing COM objects in your .NET code

Here at work, we are using a third party component where they provide a COM interface.

We're using .NET code so we are using COM interop to use the COM object.

Remember back in the VB6 days when you would set your objects to null when you're done with it? Well, so did someone else here, because that's what I saw.

I'm going to show you an example using the Microsoft Excel Type Library.

Here's some sample code that I wrote that will open up an Excel file and get the text value from a cell in the first worksheet.


    using System;
 
    using Microsoft.Office.Interop.Excel;
 
    internal class Program
    {
        #region Methods
 
        private static void Main(string[] args)
        {
            var app = new Application();
            var books = app.Workbooks;
            var book = books.Open(@"C:\Temp\doc.xlsx");
            var sheets = book.Sheets;
            var sheet = sheets.Item[1] as Worksheet;
            string cellText = string.Empty;
 
            if (sheet != null)
            {
                var cells = sheet.Cells;
                var cell = cells.Item["2""B"as Range;
                if (cell != null)
                {
                    cellText = cell.Text.ToString();
                    cells = null;
                    cell = null;
                }
 
                sheet = null;
                sheets = null;
            }
 
            app.Quit();
 
            books = null;
            book = null;
            app = null;
            Console.WriteLine(cellText);
            Console.ReadLine();
        }
 
        #endregion
    }

See what I did there, I set all of the object references to null, which is how it was done back in the VB6 days, but we're using the .NET CLR. Things are different.


So what the program does is essentially fires up an instance of excel.exe, and then does what I asked it to do which in this case is to just get text from a cell.


The problem is that after my program finishes... excel.exe is still running even though I called "app.Quit()". That's because it can't quit while I still have references to the objects. Simply setting the references to null didn't do the trick. The managed runtime of the .NET CLR doesn't know how to garbage collect unmanaged COM Objects, so even though these references are null, they won't be properly disposed.


So how do you properly dispose of these references? By using Marshal.ReleaseComObject or Marshal.FinalReleaseComObject. Click on the links to read more about each method.


Here's the new version of the program:


    using System;
    using System.Runtime.InteropServices;
 
    using Microsoft.Office.Interop.Excel;
 
    internal class Program
    {
        #region Methods
 
        private static void Main(string[] args)
        {
            var app = new Application();
            var books = app.Workbooks;
            var book = books.Open(@"C:\Temp\doc.xlsx");
            var sheets = book.Sheets;
            var sheet = sheets.Item[1] as Worksheet;
            string cellText = string.Empty;
 
            if (sheet != null)
            {
                var cells = sheet.Cells;
                var cell = cells.Item["2""B"as Range;
                if (cell != null)
                {
                    cellText = cell.Text.ToString();
                    Marshal.FinalReleaseComObject(cells);
                    Marshal.FinalReleaseComObject(cell);
                }
 
                Marshal.FinalReleaseComObject(sheet);
                Marshal.FinalReleaseComObject(sheets);
            }
 
            app.Quit();
 
            Marshal.FinalReleaseComObject(books);
            Marshal.FinalReleaseComObject(book);
            Marshal.FinalReleaseComObject(app);
            Console.WriteLine(cellText);
            Console.ReadLine();
        }
 
        #endregion
    }

Give it a try yourself. Run the app and keep an eye on task manager. When the app gets to the last line, Console.ReadLine(), you'll notice that excel.exe is still running in the first version of the code. Now run the second version and wait for the program to get to the last line, you'll notice that excel.exe is not runnning. 

Useful links:
Mixing deterministic and non-deterministic cleanup