BernieP
BernieP

Reputation: 457

C# excel interop process persists after crash

I have just recently started working with the Excel Interop in C# and have run into a problem with Excel processes persisting if my application crashes. (Why the process crashes is a separate issue that I am investigating.)

I think I am releasing the COM objects correctly as everything cleans up fine if my application completes successfully. It is only if it crashes or if I happen to quit during debugging that the Excel process is left.

Of course I realize when it crashes, the COM objects aren’t cleaned up. I am not sure how to handle this.

Here is a bit of pseudo-code that hopefully demonstrates what I am doing (the real code is rather long.)

It is supposed to 1)Open an existing excel file, 2) access a specific worksheet in the file, 3) insert a lot of rows, 4) add values to those rows, 5) close & save it all.

What am I doing wrong?

    // Open excel file
    try {
        myExcelApp = new Excel.Application();
        myExcelApp.Visible = false;
        myExcelApp.DisplayAlerts = false;
        myExcelWorkbook = (Excel.Workbook)myExcelApp.Workbooks.Open(excelFile);
    } catch (Exception ex) {
        string msg = "Error:Failed opening Excel File " + excelFile + ": " + ex.Message;
        throw new Exception(msg);
    }

    // ---- some other stuff here. ----

    foreach ( var toolWorkSheetName in workSheetsList ){

        // Init
        Excel.Worksheet xlWorksheet = null;
        Excel.Range xlRange = null;

        // Get specific worksheet from workbook
        try {
            xlWorksheet = (Excel.Worksheet)myExcelWorkbook.Worksheets[toolWorkSheetName];
        } catch (Exception ex) {
            string msg = "Error:Could not open worksheet in " + toolWorkSheetName + ": " + ex.Message;
            throw new Exception(msg);
        }

        // First scan existing template for insertion row & number of rows to insert
        xlRange = xlWorksheet.UsedRange;
        object[,] values = (object[,])xlRange.Value2;
        Marshal.ReleaseComObject(xlRange);  // Release local com objects 
        int colCount = values.GetLength(1);
        values = null;

        // ---- Determine the following: -----
        // insertRow =~ 3;
        // nLinesToInsert  =~ 63233;
        // colCount =~ 400;

        // Insert a range of rows for the values
        Excel.Range range = xlWorksheet.Range[xlWorksheet.Cells[insertRow, 1], xlWorksheet.Cells[insertRow + nLinesToInsert - 1, colCount]];
        range.Insert(Excel.XlInsertShiftDirection.xlShiftDown, Excel.XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
        Marshal.ReleaseComObject(range);

        values = new object[nLinesToInsert, colCount];

        // ---- populate the new values array ----

        // Insert the values at the target rows.
        Excel.Range startCell = (Excel.Range)xlWorksheet.Cells[insertRow, 1];
        Excel.Range endCell = (Excel.Range)xlWorksheet.Cells[insertRow + nCsvInsertRows - 1, nColsDo];
        Excel.Range writeRange = xlWorksheet.Range[startCell, endCell];
        writeRange.Value2 = values;
        Marshal.ReleaseComObject(writeRange);
        Marshal.ReleaseComObject(endCell);
        Marshal.ReleaseComObject(startCell);

        // Release local com objects 
        Marshal.ReleaseComObject(xlWorksheet);

    }

    //cleanup (NB: Does this need to be done? Does it need to be done here?)
    GC.Collect();
    GC.WaitForPendingFinalizers();

    // Save
    object misValue = System.Reflection.Missing.Value;
    myExcelWorkbook.SaveAs(outFile, Microsoft.Office.Interop.Excel.XlFileFormat.xlOpenXMLWorkbook, misValue,
                        misValue, misValue, misValue, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive,
                        misValue, misValue, misValue, misValue, misValue);

    myExcelWorkbook.Close();
    myExcelApp.Quit();

    //close and release
    Marshal.ReleaseComObject(myExcelWorkbook);
    myExcelWorkbook = null;

    //quit and release
    Marshal.ReleaseComObject(myExcelApp);
    myExcelApp = null;

Upvotes: 2

Views: 1267

Answers (2)

Mo Star
Mo Star

Reputation: 231

In your try catch, you should be closing Excel. Like so

      try{
            //Some code
        }
    catch{
       Marshal.ReleaseComObject(xlWorksheet);
        myExcelWorkbook.Close();
         myExcelApp.Quit();

     }

As it stands when it fails it does not close Excel.

Upvotes: 1

SouXin
SouXin

Reputation: 1564

You are releasing the object outside of try-catch blocs. And in the catch, you create a new exception even with a new message. When you are creating a new exception from catch block your original exception is gone. Considered as a bad practice.

You have to release objects inside the catch or finally blocks. According to your code, your objects are still persisted after the crash.

BTW, to work with Excel I would recommend the EPPlus library. It will perform all operation that you need without installing Excel on the server (bad practice again).

UPDATE

To clean-up all the objects:

           System.Runtime.InteropServices.Marshal.ReleaseComObject(startCell );    
           System.Runtime.InteropServices.Marshal.ReleaseComObject(endCell);             
         System.Runtime.InteropServices.Marshal.ReleaseComObject(writeRange);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange );
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorksheet);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange );
            startCell = null;
            endCell = null;
            writeRange = null;

            myExcelApp.Quit();

          System.Runtime.InteropServices.Marshal.ReleaseComObject(myExcelApp);
            myExcelApp = null;
            myExcelWorkbook = null;

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

Upvotes: 1

Related Questions