14
votes

i am working on a winforms c# visual studio 2008 application. the app talks to excel files and i am using Microsoft.Office.Interop.Excel; to do this.

i would like to know how can i make sure that the objects are released even when there is an error?

here's my code:

private void button1_Click(object sender, EventArgs e)
{
    string myBigFile="";
    OpenFileDialog openFileDialog1 = new OpenFileDialog();
    DialogResult result = openFileDialog1.ShowDialog(); // Show the dialog.
    if (result == DialogResult.OK) // Test result.
        myBigFile=openFileDialog1.FileName;

    Excel.Application xlApp;
    Excel.Workbook xlWorkBook;
    Excel.Worksheet xlWorkSheet;
    Excel.Range range;

    string str;
    int rCnt = 0;
    int cCnt = 0;

    xlApp = new Excel.ApplicationClass();
    xlWorkBook = xlApp.Workbooks.Open(myBigFile, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", true, false, 0, true, 1, 0);
    xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);

    range = xlWorkSheet.UsedRange;

    /*
    for (rCnt = 1; rCnt <= range.Rows.Count; rCnt++)
    {
        for (cCnt = 1; cCnt <= range.Columns.Count; cCnt++)
        {
            str = (string)(range.Cells[rCnt, cCnt] as Excel.Range).Value2;
            MessageBox.Show(str);
        }
    }
     */
    xlWorkSheet..EntireRow.Delete(Excel.XLDirection.xlUp)

    xlWorkBook.SaveAs(xlWorkBook.Path + @"\XMLCopy.xls",         Excel.XlFileFormat.xlXMLSpreadsheet, Type.Missing, Type.Missing,
   false, false, Excel.XlSaveAsAccessMode.xlNoChange,
   Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

    xlWorkBook.Close(true, null, null);
    xlApp.Quit();

    releaseObject(xlWorkSheet);
    releaseObject(xlWorkBook);
    releaseObject(xlApp);
}

private void releaseObject(object obj)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        obj = null;
    }
    catch (Exception ex)
    {
        obj = null;
        MessageBox.Show("Unable to release the Object " + ex.ToString());
    }
    finally
    {
        GC.Collect();
    }
}

how can i make sure that even if i get an error after the workbook opened, that i make sure to dispose of the objects:

Excel.Application xlApp;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
Excel.Range range;

In other words no matter what i need the following lines to run

xlWorkBook.Close(true, null, null);
xlApp.Quit();

releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlApp);

please note that i have tried this as well, resulting in the same issue

xlWorkBook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);


                xlApp.Quit();

                Marshal.ReleaseComObject(xlWorkSheet);
                Marshal.ReleaseComObject(xlWorkBook);
                Marshal.ReleaseComObject(xlApp);

                xlWorkSheet = null;
                xlWorkBook = null;
                xlApp = null;

                GC.GetTotalMemory(false);
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                GC.GetTotalMemory(true);  

and i did this as well:

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

                Marshal.FinalReleaseComObject(xlWorkSheet);

                xlWorkBook.Close(Type.Missing, Type.Missing, Type.Missing);
                Marshal.FinalReleaseComObject(xlWorkBook); 

                xlApp.Quit();
                Marshal.FinalReleaseComObject(xlApp); 

at this point i do not think it's possible to close excel from visual studio 2008. it must be a bug or something, but i've tried the top 20 websites on this and getting the same result: excel is opening two instances for some reason and when i do the garbage collection etc.. (or not) it closes just ONE instance.

when i try to open the file, it says there's an error or it's corrupt.

when i go to task manager and kill the excel process, the file will open without problems.]

is there a way to close excel with visual studio 2008? if so, can you please provide me with guidance or a solution to this

2
can you please tell me the difference between what is happening with xlworkbook.close and xlapp.quit vs the releaseObject ? why do i need both of these?Alex Gordon
This answers your question nicely.Jay Riggs
@JayRiggs but erik said specifically not to use gc.collectAlex Gordon
@ErikPhilips jay quoted someone who said to use gc.collection twice!Alex Gordon

2 Answers

22
votes

First I will present a modified releaseObject, and then I will provide a pattern to use it.

using Marshal = System.Runtime.InteropServices.Marshal;
private void releaseObject(ref object obj) // note ref!
{
    // Do not catch an exception from this.
    // You may want to remove these guards depending on
    // what you think the semantics should be.
    if (obj != null && Marshal.IsComObject(obj)) {
        Marshal.ReleaseComObject(obj);
    }
    // Since passed "by ref" this assingment will be useful
    // (It was not useful in the original, and neither was the
    //  GC.Collect.)
    obj = null;
}

Now, a pattern to use:

private void button1_Click(object sender, EventArgs e)
{
    // Declare. Assign a value to avoid a compiler error.
    Excel.Application xlApp = null;
    Excel.Workbook xlWorkBook = null;
    Excel.Worksheet xlWorkSheet = null;

    try {
        // Initialize
        xlApp = new Excel.ApplicationClass();
        xlWorkBook = xlApp.Workbooks.Open(myBigFile, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", true, false, 0, true, 1, 0);
        // If the cast fails this like could "leak" a COM RCW
        // Since this "should never happen" I wouldn't worry about it.
        xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
        ...
    } finally {
        // Release all COM RCWs.
        // The "releaseObject" will just "do nothing" if null is passed,
        // so no need to check to find out which need to be released.
        // The "finally" is run in all cases, even if there was an exception
        // in the "try". 
        // Note: passing "by ref" so afterwords "xlWorkSheet" will
        // evaluate to null. See "releaseObject".
        releaseObject(ref xlWorkSheet);
        releaseObject(ref xlWorkBook);
        // The Quit is done in the finally because we always
        // want to quit. It is no different than releasing RCWs.
        if (xlApp != null) {
            xlApp.Quit();
        }
        releaseObject(ref xlApp);    
    }
}

This simple approach can be extended/nested over most situations. I use a custom wrapper class that implements IDisposable to make this task easier.

4
votes

Verify that there are two problems you're seeing in your code:

  • That when the program closes Excel remains as a running process
  • That when you open the Excel file your program creates you see an error in Excel saying the file is corrupted or some such

I copied the button1 click handler and pst's releaseObject method in your edited question into a clean VS2008, C#3.5 Winform application and made a couple minor changes to eliminate both the problems I listed above.

To fix Excel not unloading from memory, call releaseObject on the range object you created. Do this before your call to releaseObject(xlWorkSheet); Remembering all these references is what makes COM Interop programming so much fun.

To fix the corrupt Excel file problem update your WorkBook.SaveAs method call to replace the second parameter (Excel.XlFileFormat.xlXMLSpreadsheet) with Type.Missing. The SaveAs method will handle this correctly by default.

I'm sure the code you posted in your question is simplified to help debug the problems you're having. You should use the try..finally block pst demonstrates.