9

I'm reading some data from a sheet of an excel file using interop library. I get the data just fine by using Microsoft.Office.Interop.Excel and then reading all data in the sheet.

Application ExcelApp = new Excel.Application();
Workbook excelWorkbook = ExcelApp2.Workbooks.Open(excelFileNamePath);
_Worksheet excelWorksheet = excelWorkbook.Sheets[excelSheetName];
Range excelRange = excelWorksheet.UsedRange;
//...for loops for reading data
ExcelApp.Quit();

But after my application terminates, I tried to edit my excel file and had some problems while opening. Apparently the excel process keeps on running in the background even though I called ExcelApp.Quit(). Is there a way to properly close the excel file that the application uses? I'm new to c# so i'm probably missing something here.

Edit: Solved! Apparently there's a rule for com objects that discourages using 2 dots.

Excel.Application ExcelApp2 = new Excel.Application();
Excel.Workbooks excelWorkbooks = ExcelApp2.Workbooks;
Excel.Workbook excelWorkbook = excelWorkbooks.Open(excelFileName);
Excel._Worksheet excelWorksheet = excelWorkbook.Sheets[excelSheetName];
//...
excelWorkbook.Close();       
Marshal.ReleaseComObject(excelWorkbook);
Marshal.ReleaseComObject(excelWorksheet);
Marshal.ReleaseComObject(excelRange);
ExcelApp2.Quit();
Community
  • 1
  • 1
rrh
  • 105
  • 1
  • 2
  • 8

8 Answers8

15

There was another similar question - and answer (https://stackoverflow.com/a/17367570/3063884), in which the solution was to avoid using double-dot notation. Instead, define variables for each object used along the way, and individually use Marshal.ReleaseComObject on each one.

Copying straight from the linked solution:

var workbook = excel.Workbooks.Open(/*params*/)

---> instead use -->

var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)

Then, when done, release each COM object:

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
Community
  • 1
  • 1
CJBS
  • 15,147
  • 6
  • 86
  • 135
4

Long time no answer but what worked for me was to call the GC.Collect(); from the caller,

i.e. instead of

main()
{
    ...
    doexcelstuff();
    ...
}

void doexcelstuff()
{
    Excel.Application ExApp2 = new Excel.Application();
    Excel.Workbook excelWb = ExApp2 .Workbooks.Open(excelFName);
    Excel._Worksheet excelWorksheet = excelWb.Sheets[excelSName];
    //...
    excelWb.Close();
    ExApp2.Quit();     
    Marshal.ReleaseComObject(excelWb);
    Marshal.ReleaseComObject(excelWorksheet);
    Marshal.ReleaseComObject(ExApp2);
    excelWb = null;
    excelWorksheet= null;
    ExApp2= null;
    GC.Collect();
}

Using above Excel does not die

but a very small change, to where the GC is called from

main()
{
    ...
    doexcelstuff();
    GC.Collect();      // <<-- moved the GC to here (the caller)
    ...
}

void doexcelstuff()
{
    Excel.Application ExApp2 = new Excel.Application();
    Excel.Workbook excelWb = ExApp2 .Workbooks.Open(excelFName);
    Excel._Worksheet excelWorksheet = excelWb.Sheets[excelSName];
    //...
    excelWb.Close();
    ExApp2.Quit();     
    Marshal.ReleaseComObject(excelWb);
    Marshal.ReleaseComObject(excelWorksheet);
    Marshal.ReleaseComObject(ExApp2);
    excelWb = null;
    excelWorksheet= null;
    ExApp2= null;
    // removed the GC from here
}

My guess is the garbage collector needs to also quietly clean up internally created temp values (including refs/pointers) from the heap - some of which I guess in this case are pointing to COM objects.

(Just takes a smidgen of understanding of how machines work underneath the source code.)

Rob
  • 444
  • 3
  • 10
1

You are not closing your workbook. Close it and then release it before quitting the Excel application:

excelWorkbook.close();
Marshal.ReleaseComObject(excelWorkbook);
ExcelApp.Quit();
Brian English
  • 466
  • 2
  • 8
  • The error still pops up. It says "The document with the name ... is already open" – rrh Feb 26 '14 at 20:02
1

For me this worked!

//Initializing
Application excelApp = new Application();
Workbook excelWorkbook = excelApp.Workbooks.Open(pathExcelFile, 0, true, 5, "", "", 
                               true, XlPlatform.xlWindows, "\t", false, false, 0, 
                               true, 1, 0);
Worksheet excelWorksheet = (Worksheet)excelWorkbook.Sheets[1];

//closing
excelWorksheet = null;

excelWorkbook.Close();
excelWorkbook = null;

excelApp.Quit();
excelApp = null;
TJacken
  • 354
  • 3
  • 12
0

Interop is notoriously buggy... I use the following method after saving my workbook, and no longer have issues with Excel remaining open when I exit my applications:

while (Marshal.ReleaseComObject(wb) > 0);
while (Marshal.ReleaseComObject(xl) > 0);

wb = null;
xl = null;

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

wb is my workbook object, and xl is my Excel.Application object.

John
  • 186
  • 5
  • That doesn't seem to work either. I can see how buggy it is now, this is so annoying! – rrh Feb 26 '14 at 20:06
  • Are you saving and closing your stuff before you call the code above? I do this: wb.SaveAs( _saveFile, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookDefault, Type.Missing, Type.Missing, false, false, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing ); wb.Saved = true; – John Feb 26 '14 at 20:13
  • FWIW, the `FinalReleaseComObject` method releases the managed reference to a COM object. Calling this method is equivalent to calling the `ReleaseComObject` method in a loop until it returns 0 (zero), as you've done above. And in my case, I didn't need to set the objects to null before calling `GC.Collect()`. I also don't see a need to call that method twice and the call to `GC.WaitForPendingFinalizers()` should occur _after_ the call to `GC.Collect()`. – Mike Lowery Jul 30 '21 at 19:08
0

For me this worked : //opening

        var excelFile = new Application();
        Workbook workBook1 = excelFile.Workbooks.Open(goldenCopy_path);
        Workbook workBook2 = excelFile.Workbooks.Open(new_path);

        Worksheet goldWorkSheet, newWorkSheet;
        goldWorkSheet = workBook1.Worksheets[1];
        newWorkSheet = workBook2.Worksheets[1];

//closing //it will release the workbook from visual studio

        goldWorkSheet = null;
        newWorkSheet = null;

        workBook1.Close();
        workBook2.Close();
        workBook1 = null;
        workBook2 = null;

        excelFile.Quit();
        excelFile = null;
0

This code worked on me.

        Excel.Application excelApp = null;
        Excel.Workbooks excelWorkbooks = null;
        Excel.Workbook excelWorkbook = null;
        Excel._Worksheet xlWorkSheet = null;
        Excel.Range range = null;

        excelApp = new Excel.Application();
        excelWorkbooks = excelApp.Workbooks;
        excelWorkbook = excelWorkbooks.Open(excelName);
        xlWorkSheet = (Excel.Worksheet)excelWorkbook.ActiveSheet;

        range = xlWorkSheet.Range["C3"] ;
        range.Value = "Update Data";
        Marshal.ReleaseComObject(range);

        xlWorkSheet.SaveAs(path);
            
        Marshal.ReleaseComObject(xlWorkSheet);
        excelWorkbook.Close();
        Marshal.ReleaseComObject(excelWorkbook);
        excelWorkbooks.Close();
        Marshal.ReleaseComObject(excelWorkbooks);
        excelApp.Quit();
        Marshal.ReleaseComObject(excelApp);
onoffon
  • 93
  • 10
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 10 '22 at 08:06
-1
var Process = Process.GetProcessesByName("excel").Select(x => x.Id.ToString());
Application ExcelApp = new Excel.Application();
Workbook excelWorkbook = ExcelApp2.Workbooks.Open(excelFileNamePath);
_Worksheet excelWorksheet = excelWorkbook.Sheets[excelSheetName];
Range excelRange = excelWorksheet.UsedRange;
//...for loops for reading data
foreach (Process pros in Process.GetProcessesByName("excel"))
{
    if (!array1.Contains(pros.Id.ToString()))
    {
        Console.WriteLine(pros.Id.ToString());
        pros.Kill();

    }

}
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 03 '23 at 17:16