How to clean up Excel interop objects in C# - c#

When I run following code it still doesn't releasing excel object.
public static List<string> GetExcelSheets(string FilePath)
{
Microsoft.Office.Interop.Excel.Application ExcelObj = null;
Workbook theWorkbook = null;
Sheets sheets = null;
try
{
ExcelObj = new Microsoft.Office.Interop.Excel.Application();
if (ExcelObj == null)
{
MessageBox.Show("ERROR: EXCEL couldn't be started!");
System.Windows.Forms.Application.Exit();
}
theWorkbook = ExcelObj.Workbooks.Open(FilePath, 0, true, 5, "", "", true, XlPlatform.xlWindows, "\t", false, false, 0, true);
List<string> excelSheets = new List<string>();
sheets = theWorkbook.Worksheets;
foreach (Worksheet item in sheets)
{
excelSheets.Add(item.Name);
}
return excelSheets;
}
catch (Exception ex)
{
return new List<string>();
}
finally
{
// Clean up.
releaseObject(sheets);
releaseObject(theWorkbook);
releaseObject(ExcelObj);
}
}
private static void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occured while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}

You forgot to keep a reference to ExcelObj.Workbooks and to release it:
Workbooks workbooks;
...
workbooks = ExcelObj.Workbooks;
theWorkbook = workbooks.Open(...
...
releaseObject(sheets);
releaseObject(theWorkbook);
releaseObject(workbooks);
releaseObject(ExcelObj);

Try this code:
ExcelObj.Quit();

Related

Disposing of COM object from c#

I have read several articles/posts on correctly disposing of COM objects when interacting from .NET. Normally i don't have a problem.. but i keep getting orphaned COM objects after closing my following program.I don't know if it has something to do with the addin i am opening.
Application app = new Application();
Workbooks wrks = app.Workbooks;
Workbook wrk = null;
Workbook wrkAddin = null;
Sheets shts = null;
Range rng = null;
try
{
app.ScreenUpdating = true;
app.Visible = true;
app.DisplayAlerts = false; // stop any dialog boxes appearing.. such as save or debug when loading bloomberg addin.
wrk = wrks.Open(Filename: MasterPriceFiles.Properties.Resources.strETFDataFullPath, ReadOnly: false,Editable: true, IgnoreReadOnlyRecommended: true);
Thread.Sleep(500);
wrkAddin = wrks.Open(Filename: MasterPriceFiles.Properties.Resources.strBloombergAddinPath); // have to explicitly open the bloomberg addin
app.Visible = true;
Thread.Sleep(50000);
shts = wrk.Worksheets;
foreach (Worksheet sht in shts)
{
rng = sht.UsedRange;
rng.Copy();
rng.PasteSpecial(XlPasteType.xlPasteValues);
Marshal.ReleaseComObject(rng);
Marshal.ReleaseComObject(sht);
}
app.Visible = false;
app.DisplayAlerts = false;
Thread.Sleep(500);
wrk.SaveAs(Filename: MasterPriceFiles.Properties.Resources.strETFDataMacroFreeFullPath, ConflictResolution: XlSaveConflictResolution.xlLocalSessionChanges, AccessMode: XlSaveAsAccessMode.xlNoChange);
Console.WriteLine("Successfully saved ETF price data sheet at . " + DateTime.Now.ToString());
}
catch(Exception ex)
{
Console.WriteLine("Error refreshing ETF price date. " + ex.Message);
}
finally
{
if (rng != null) Marshal.FinalReleaseComObject(rng);
if (shts != null) Marshal.FinalReleaseComObject(shts);
wrk.Close(SaveChanges: false);
if (wrk != null) Marshal.FinalReleaseComObject(wrk);
wrkAddin.Close(SaveChanges: false);
if (wrkAddin != null) Marshal.FinalReleaseComObject(wrkAddin);
if (wrks != null) Marshal.FinalReleaseComObject(wrks);
app.Quit();
if (app != null) Marshal.FinalReleaseComObject(app);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}

Excel Not closing properly even after Releasing Objects/Variables

After reading some posts and trying some things. I am still not getting excel to close properly after releasing the objects.
I do the following below:
Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
if (xlApp == null)
{
//MessageBox.Show("Excel is not properly installed!!");
return;
}
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
object misValue = System.Reflection.Missing.Value;
if (!System.IO.File.Exists("file.xlsx"))
{
xlWorkBook = xlApp.Workbooks.Add(misValue);
}
else
{
xlWorkBook = xlApp.Workbooks.Open("file.xlsx", 0, false, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", true, false, 0, true, 1, 0);
}
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
xlWorkSheet.Name = "Sheet Name";
Then close excel and get rid of the objects:
xlApp.DisplayAlerts = false;
xlWorkBook.SaveAs("file.xlsx");
xlWorkBook.Close(true, "file.xlsx", misValue);
xlApp.Application.Quit();
xlApp.Quit();
Marshal.ReleaseComObject(xlWorkSheet);
Marshal.ReleaseComObject(xlWorkBook);
Marshal.ReleaseComObject(xlApp);
xlApp = null;
even after I do this I still see excel.exe in the task manager. Can someone help me out with what i am doing wrong here. I would really appreciate it.
here is what i have been using to kill the process. it works, but if someone has a more elegant solution i'd be happy to know as well!
private void releaseObject(object obj)
{
try
{
Marshal.ReleaseComObject(obj);
obj = null;
var process = System.Diagnostics.Process.GetProcessesByName("Excel");
foreach (var p in process)
{
if (!string.IsNullOrEmpty(p.ProcessName))
{
try
{
p.Kill();
}
catch { }
}
}
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Unable to release the Excel Object " + ex.ToString());
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
call this on each object
releaseObject(worksheet);
releaseObject(workbook);
releaseObject(xlapplication);

Excel.Interop.Chart.Export does not close exported image file

I have made a small class that takes an excel workbook, graps the contained charts in the workbook and export each of them to PNG files.
It almost works fine, but I have a problem regarding the generated PNG files, the class doesn't close the files properly, until the user closes the application.
Here is my class:
public List<string> ExportCharts(string fileName)
{
var exportedGraphsList = new List<string>();
Excel.Workbooks xlWorkBooks = null;
Excel.Workbook xlWorkBook = null;
Excel.Application xlApp = null;
Excel.Sheets xlSheets = null;
object misValue = System.Reflection.Missing.Value;
try
{
xlApp = new Excel.ApplicationClass();
xlWorkBooks = xlApp.Workbooks;
xlWorkBook = xlWorkBooks.Open(fileName);
xlSheets = xlWorkBook.Charts;
foreach (Excel.Chart xlChart in xlSheets)
{
var exportfileName = ConfigurationManager.AppSettings["imageSavePath"] + #"\" + xlChart.Name + ".png";
xlChart.Export(exportfileName, "PNG", misValue);
exportedGraphsList.Add(exportfileName);
}
return exportedGraphsList;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace, "Der gik noget galt");
exportedGraphsList.Clear();
return exportedGraphsList;
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
if(xlSheets != null)
Marshal.FinalReleaseComObject(xlSheets);
if (xlWorkBook != null)
{
xlWorkBook.Close(false, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlWorkBook);
}
if (xlWorkBooks != null)
Marshal.FinalReleaseComObject(xlWorkBooks);
if (xlApp != null)
{
xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);
}
}
}
public void Dispose()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
When this method is invoked, it starts and closes the Excel proces very nicely, but the PNG files remains open. I could of coruse put a timestamp on the exported filesnames, but I fear it will confuse the user.
Thanks in advance for any hints or ideas. :-)
Solved - it was due to the fact that I loaded the image files afterwards to the GUI.
So Excel.InterOp works fine.

In Excel VSTO, how can I check if a worksheet belongs to a closed workbook?

If I have a reference to Worksheet and I close it's parent Workbook, the reference doesn't go away. But I can't figure out how I should check to make sure these sheets don't exist. Checking for null doesn't work.
Example:
Workbook book = Globals.ThisAddIn.Application.ActiveWorkbook;
Worksheet sheet = (Worksheet)book.Worksheets[1]; // Get first worksheet
book.Close(); // Close the workbook
bool isNull = sheet == null; // false, worksheet is not null
string name = sheet.Name; // throws a COM Exception
This is the exception I get when I try to access the sheet:
System.Runtime.InteropServices.COMException was caught
HResult=-2147221080
Message=Exception from HRESULT: 0x800401A8
Source=MyProject
ErrorCode=-2147221080
StackTrace:
at Microsoft.Office.Interop.Excel._Worksheet.get_Name()
at MyCode.test_Click(Object sender, RibbonControlEventArgs e) in c:\MyCode.cs:line 413
InnerException:
This wouldn't even be an issue if I could check for a workbook delete event, but Excel doesn't provide one (which is really annoying).
Is there some convenient way to make sure I don't use these worksheets?
If the other solutions fail, another way to handle this is to store the name of the workbook after it opens, then check to see if that name exists in the Workbooks collection before referencing the sheet. Referencing the workbooks by name will work since you can only have uniquely named workbooks in each instance of Excel.
public void Test()
{
Workbook book = Globals.ThisAddIn.Application.ActiveWorkbook;
string wbkName = book.Name; //get and store the workbook name somewhere
Worksheet sheet = (Worksheet)book.Worksheets[1]; // Get first worksheet
book.Close(); // Close the workbook
bool isNull = sheet == null; // false, worksheet is not null
string name;
if (WorkbookExists(wbkName))
{
name = sheet.Name; // will NOT throw a COM Exception
}
}
private bool WorkbookExists(string name)
{
foreach (Microsoft.Office.Interop.Excel.Workbook wbk in Globals.ThisAddIn.Application.Workbooks)
{
if (wbk.Name == name)
{
return true;
}
}
return false;
}
Edit: for completeness, a helper extension method:
public static bool SheetExists(this Excel.Workbook wbk, string sheetName)
{
for (int i = 1; i <= wbk.Worksheets.Count; i++)
{
if (((Excel.Worksheet)wbk.Worksheets[i]).Name == sheetName)
{
return true;
}
}
return false;
}
I use this method :
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occured while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
or you can try something like this:
static bool IsOpened(string wbook)
{
bool isOpened = true;
Excel.Application exApp;
exApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
try
{
exApp.Workbooks.get_Item(wbook);
}
catch (Exception)
{
isOpened = false;
}
return isOpened;
}
I've not tried this, but you could check if the Workbook sheet.Parent exists in the Application.Workbooks collection.

Excel objects not getting disposed sometimes

I have this piece of code for reading from an excel cell:
public T GetValue<T>(string testsheet, string range)
{
Application excelApplication = null;
Workbooks workBooks = null;
Workbook activeWorkBook = null;
Worksheet activeWorkSheet = null;
try
{
excelApplication = new Application();
workBooks = excelApplication.Workbooks;
activeWorkBook = workBooks.Open(workBook);
activeWorkSheet = activeWorkBook.ActiveSheet;
var cells = activeWorkSheet.get_Range(range);
return cells.Value2;
}
catch (Exception theError)
{
Console.WriteLine(theError.Message);
throw theError;
}
finally
{
ReleaseComObject(activeWorkSheet);
ReleaseComObject(activeWorkBook);
ReleaseComObject(workBooks);
ReleaseComObject(excelApplication);
}
}
Also I have a Set value method which sets the value to the cell as below:
public void SetValue<T>(string testsheet, string range, T value)
{
Application excelApplication = null;
Workbooks workBooks = null;
Workbook activeWorkBook = null;
Worksheet activeWorkSheet = null;
try
{
excelApplication = new Application();
workBooks = excelApplication.Workbooks;
activeWorkBook = workBooks.Open(workBook);
activeWorkSheet = activeWorkBook.ActiveSheet;
var cells = activeWorkSheet.get_Range(range);
cells.Value2 = value;
activeWorkBook.Save();
}
catch (Exception theError)
{
Console.WriteLine(theError.Message);
throw theError;
}
finally
{
if (activeWorkBook != null)
activeWorkBook.Close();
ReleaseComObject(excelApplication);
ReleaseComObject(workBooks);
ReleaseComObject(activeWorkBook);
ReleaseComObject(activeWorkSheet);
}
}
Here is my ReleaseComObject:
private static void ReleaseComObject<T>(T comObject) where T : class
{
if (comObject != null)
Marshal.ReleaseComObject(comObject);
}
I have written a test to ensure that the excel objects are properly disposed as below:
[Test]
public void Should_dispose_excel_objects_created_after_io_operation()
{
var expected = Process.GetProcesses().Count(process => process.ProcessName.ToLower() == "excel");
var automationClient = new ExcelAutomation.ExcelAutomationClient(ExcelSheet);
automationClient.SetValue("Sheet1", "A1", 1200);
automationClient.GetValue<double>("Sheet1", "A1");
var actual = Process.GetProcesses().Count(process => process.ProcessName.ToLower() == "excel");
actual.Should().Be(expected, "Excel workbook is not disposed properly. There are still excel processess in memory");
}
This test passess if I'm invoking only SetValue method, however while invoking GetValue it fails. However I can see no Excel.exe in the taskmanager. Any idea why this is happening please? Is something wrong with my GetValue function?
When you done disposing the object, use
GC.Collect();
This is how I dispose my Excel object
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occured while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}

Categories