9

I've got this C# program that never closes the Excel process. Basically it finds the number of instances a string appears in a range in Excel. I've tried all kinds of things, but it's not working. There is a Form that is calling this method, but that shouldn't change why the process isn't closing. I've looks at suggestions by Hans Passant, but none are working.

EDIT: I tried the things mentioned and it still won't close. Here's my updated code. EDIT: Tried the whole Process.Kill() and it works, but it seems like a bit of a hack for something that should just work.

public class CompareHelper
{
    // Define Variables
    Excel.Application excelApp = null;
    Excel.Workbooks wkbks = null;
    Excel.Workbook wkbk = null;
    Excel.Worksheet wksht = null;
    Dictionary<String, int> map = new Dictionary<String, int>();

    // Compare columns
    public void GetCounts(string startrow, string endrow, string columnsin, System.Windows.Forms.TextBox results, string excelFile)
    {
        results.Text = "";

        try
        {
            // Create an instance of Microsoft Excel and make it invisible
            excelApp = new Excel.Application();
            excelApp.Visible = false;

            // open a Workbook and get the active Worksheet
            wkbks = excelApp.Workbooks;
            wkbk = wkbks.Open(excelFile, Type.Missing, true);
            wksht = wkbk.ActiveSheet;
            ...

        }
        catch
        {
            throw;
        }
        finally
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();

            if (wksht != null)
            {
                //wksht.Delete();
                Marshal.FinalReleaseComObject(wksht);
                wksht = null;
            }

            if (wkbks != null)
            {
                //wkbks.Close();
                Marshal.FinalReleaseComObject(wkbks);
                wkbks = null;
            }

            if (wkbk != null)
            {
                excelApp.DisplayAlerts = false;
                wkbk.Close(false, Type.Missing, Type.Missing);
                Marshal.FinalReleaseComObject(wkbk);
                wkbk = null;
            }

            if (excelApp != null)
            {
                excelApp.Quit();
                Marshal.FinalReleaseComObject(excelApp);
                excelApp = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();

            /*
            Process[] processes = Process.GetProcessesByName("EXCEL");
            foreach (Process p in processes)
            {
                p.Kill();
            }
            */
        }
    }
}
user1017719
  • 101
  • 1
  • 1
  • 5
  • A smaller repro would help. Anyway, I ran this on my machine and Excel closes just fine. Do you have any badly-behaved Excel add-ins installed? Try removing them one by one to see if that affects Excel's ability to shut down cleanly. – Richard Cook Aug 09 '13 at 19:49
  • I tried a vanilla Excel process and it still didn't close. I am using a Form as the interface to call this class, could that be doing something to keep Excel open? – user1017719 Aug 09 '13 at 20:03
  • It's actually the third answer I think you will want from the above link. – devuxer Aug 09 '13 at 22:03
  • FYI, never do "catch {throw;}". It's the same as having no "catch". – John Saunders Aug 13 '13 at 22:13

3 Answers3

5

Here is an interesting knowledge base on the subject of office apps staying open after a .NET app disconnects from them.

Office application does not quit after automation from Visual Studio .NET client

The code examples are all in the link (vb.net sorry). Basically it shows you how to correctly setup and tear down the office app so that it closes when you're finished with it.

System.Runtime.InteropServices.Marshal.FinalReleaseComObject is where the magic happens.

EDIT: You need to call the FinalReleaseComObject for each excel object that you've created.

if (excelWorkSheet1 != null)
{
    Marshal.FinalReleaseComObject(excelWorkSheet1);
    excelWorkSheet1 = null;
}
if (excelWorkbook != null)
{
    Marshal.FinalReleaseComObject(excelWorkbook);
    excelWorkbook = null;
}
if (excelApp != null)
{
    Marshal.FinalReleaseComObject(excelApp);
    excelApp = null;
}
JeremiahDotNet
  • 910
  • 4
  • 9
2

I finally got it to close. You need to add a variable for the Workbooks collection, and then use the FinalReleaseComObject as stated in the other answers. I guess every possible Excel COM object that you use must be disposed this way.

try
        {
           // Create an instance of Microsoft Excel and make it invisible
           excelApp = new Excel.Application();
           excelApp.DisplayAlerts = false;
           excelApp.Visible = false;

           // open a Workbook and get the active Worksheet

           excelWorkbooks = excelApp.Workbooks;
           excelWorkbook = excelWorkbooks.Open(excelFile, Type.Missing, true);
           excelWorkSheet1 = excelWorkbook.ActiveSheet;

        }
        catch
        {
           throw;
        }
        finally
        {

           NAR( excelWorkSheet1 );
           excelWorkbook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
           NAR(excelWorkbook);
           NAR(excelWorkbooks);
           excelApp.Quit();
           NAR(excelApp);

        }
     }
     private void NAR(object o)
     {
        try
        {
           System.Runtime.InteropServices.Marshal.FinalReleaseComObject( o );
        }
        catch { }
        finally
        {
           o = null;
        }
     }
Darrin Doherty
  • 840
  • 1
  • 6
  • 17
  • I tried this and it still doesn't work. Is there a way I can find the Process ID of excelApp and just kill it? – user1017719 Aug 09 '13 at 20:42
  • You can add that in for cases where you stop Excel while debugging / crash. But it is not recommended for normal use. Comment out sections and troubleshoot that way which section is giving you the problem. – Gerhard Powell Aug 09 '13 at 22:01
  • I used your exact code sample, exhibiting the same problem, and once I added the step to NAR the workbooks it resolved the issue. – Darrin Doherty Aug 12 '13 at 13:55
2

DotNet only release the COM object after all the handles have been released. What I do is comment everything out, and then add back a portion. See if it release Excel. If it did not follow the following rules. When it release, add more code until it does not release again.

1) When you create your Excel variables, set all the values to null (this avoid not initiated errors)

2) Do not reuse variables without releasing it first Marshal.FinalReleaseComObject

3) Do not double dot (a.b = z). dotNet create a temporary variable, which will not get released.

c = a.b;
c = z;
Marshal.FinalReleaseComObject(c);

4) Release ALL excel variables. The quicker the better.

5) Set it back to NULL.

Set culture to "en-US". There is a bug that crash Excel with some cultures. This ensure it won't.

Here is an idea of how your code should be structured:

        thisThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
        InteropExcel.Application excelApp = null;
        InteropExcel.Workbooks wkbks = null;
        InteropExcel.Workbook wkbk = null;
        try
        {
                excelApp = new InteropExcel.Application();
                wkbks = excelApp.Workbooks;
                wkbk = wkbks.Open(fileName);
...

        }
        catch (Exception ex)
        {
        }

        if (wkbk != null)
        {
            excelApp.DisplayAlerts = false;
            wkbk.Close(false);
            Marshal.FinalReleaseComObject(wkbk);
            wkbk = null;
        }

        if (wkbks != null)
        {
            wkbks.Close();
            Marshal.FinalReleaseComObject(wkbks);
            wkbks = null;
        }

        if (excelApp != null)
        {
            // Close Excel.
            excelApp.Quit();
            Marshal.FinalReleaseComObject(excelApp);
            excelApp = null;
        }

        // Change culture back from en-us to the original culture.
        thisThread.CurrentCulture = originalCulture;    
    }
Gerhard Powell
  • 5,965
  • 5
  • 48
  • 59