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);
Related
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.
I am currently trying to email the graph/chart from a .xls as an image.
I can get the graph/chart send the email fine.
My issue is when i look in the task manager there is a "EXCEL.EXE" still running after i have called xlApp.quit()
Any help would be appreciated.
Here is the code i am currently using.
Excel.Application xlApp;
Excel.Workbooks xlBooks;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
Excel.ChartObject xlChartObject;
Excel.Chart xlChart;
object misValue = System.Reflection.Missing.Value;
xlApp = new Excel.Application();
xlBooks = xlApp.Workbooks;
xlWorkBook = xlBooks.Add(Properties.Settings.Default.FileToSend);
xlWorkSheet = xlWorkBook.Sheets[1];
xlWorkSheet.EnablePivotTable = true;
string filename = System.IO.Path.GetTempFileName();
xlChartObject = xlWorkSheet.ChartObjects(1);
xlChart = xlChartObject.Chart;
xlChart.Export(filename + ".gif");
xlWorkBook.Close(false, misValue, misValue);
xlBooks.Close();
xlApp.Application.Quit();
if (xlChart != null)
Marshal.ReleaseComObject(xlChart); xlChart = null;
if (xlChartObject != null)
Marshal.ReleaseComObject(xlChartObject); xlChartObject = null;
if (xlWorkSheet != null)
Marshal.ReleaseComObject(xlWorkSheet); xlWorkSheet = null;
if (xlWorkBook != null)
Marshal.ReleaseComObject(xlWorkBook); xlWorkBook = null;
if (xlBooks != null)
Marshal.ReleaseComObject(xlBooks); xlBooks = null;
if (xlApp != null)
Marshal.ReleaseComObject(xlApp); xlApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Ok have edited my code and still not closing excel.
It does however die when i exit the program.
Thanks
You need to free every object that you use.
It seems that xlApp.Workbooks is used by is not freed.
As a side note, it could also be that there was an exception and thus your cleanup code was missed.
Try something using a try/catch/finally like the following:
Excel.Application xlApp = null;
Excel.Workbook xlWorkBook = null;
Excel.Workbooks xlWorkBooks = null;
Excel.Worksheet xlWorkSheet = null;
object misValue = System.Reflection.Missing.Value;
try
{
xlApp = new Excel.Application();
xlWorkBooks = xlApp.Workbooks;
xlWorkBook = xlWorkBooks.Add(Properties.Settings.Default.FileToSend);
xlWorkSheet = xlWorkBook.Sheets[1];
xlWorkSheet.EnablePivotTable = true;
string filename = System.IO.Path.GetTempFileName();
xlWorkSheet.ChartObjects("Chart 1").Chart.Export(filename + ".gif");
xlWorkBook.Close(false, misValue, misValue);
xlApp.Quit();
}
catch(Exception ex)
{
// handle error...
}
finally
{
if (xlWorkSheet != null)
Marshal.ReleaseComObject(xlWorkSheet);
if (xlWorkBook != null)
Marshal.ReleaseComObject(xlWorkBook);
if (xlWorkBooks != null)
Marshal.ReleaseComObject(xlWorkBooks);
if (xlApp != null)
Marshal.ReleaseComObject(xlApp);
}
Try adding a second gc.collect after.
GC.Collect();
GC.WaitForPendingFinalizers(); //gc calls finalize on objects
GC.Collect(); //collect objects just finalized
mehow mentioned implementing IDisposable. I would recommend this as well.
Yay finally!
Got it working.
I added
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
out of the function that i was using EXCEL.
private void runExcelWork()
{
//xlApp, xlBooks, xlWorksheet etc.. Is defined in this function
//Do your work with Excel here.
//Clean all excel objects here.
}
public void runExcel()
{
runExcelWork();
//call GC
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
//at this point EXCEL.EXE closes
}
Thank you to all who helped me with this issue!
I hope someone else finds this useful.
Have you tried something along the lines of:
xlWorkBook.Close();
xlApp.Application.Quit(false);
xlApp = null;
This should clean up any remaining excel.exe processes
You could try to kill the process directly. (Processname could be in capital letters)
try
{
foreach (var process in Process.GetProcessesByName("excel"))
{
process.Kill();
}
}
I am trying to release Excel objects after using them. Here is my code:
Excel._Application app = new Excel.Application();
Excel._Workbook workbook = app.Workbooks.Add(Type.Missing);
Excel._Worksheet worksheet = null;
app.Visible = true;
worksheet.Cells[1, 1] = "test string";
workbook.SaveAs("C:\testfile.xlsx");
object misValue = System.Reflection.Missing.Value;
workbook.Close(true, misValue, misValue);
app.Quit();
releaseObject(worksheet);
releaseObject(workbook);
releaseObject(app);
My releaseObject method:
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Unable to release the Object " + ex.ToString());
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
At the first time the debugger goes into releaseObject, it gives an error in first "GC.WaitForPendingFinalizers();" line:
Context 0xdf76a0' is disconnected. Releasing the interfaces from the
current context (context 0xdf7450). This may cause corruption or data
loss. To avoid this problem, please ensure that all
contexts/apartments stay alive until the application is completely
done with the RuntimeCallableWrappers that represent COM components
that live inside them.
How can I figure it out?
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
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.
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.
I'm running into an issue with some code I'm debugging. Excel interop is used to extract some values from a workbook; however, Excel remains open after the program has exited. I've tried the traditional solution, but it still keeps a reference to Excel open on all machines where the code is run
private void TestExcel()
{
Excel.Application excel = new Excel.Application();
Excel.Workbooks books = excel.Workbooks;
Excel.Workbook book = books.Open("C:\\test.xlsm");
book.Close();
books.Close();
excel.Quit();
Marshal.ReleaseComObject(book);
Marshal.ReleaseComObject(books);
Marshal.ReleaseComObject(excel);
}
Even this simple piece of code keeps the process running with multiple files (xlsm, xlsx, xls). Right now we have a workaround in place to kill the Excel processes we've opened, but I'd much rather get this working for my own sanity.
I should add that I have it narrowed down to the Workbook variable. If I remove the call to books.Open() and all references to book then it closes successfully.
This has worked successfully for me:
xlApp.Quit();
//release all memory - stop EXCEL.exe from hanging around.
if (xlWorkBook != null) { Marshal.ReleaseComObject(xlWorkBook); } //release each workbook like this
if (xlWorkSheet != null) { Marshal.ReleaseComObject(xlWorkSheet); } //release each worksheet like this
if (xlApp != null) { Marshal.ReleaseComObject(xlApp); } //release the Excel application
xlWorkBook = null; //set each memory reference to null.
xlWorkSheet = null;
xlApp = null;
GC.Collect();
This code works for me.
//Declare separate object variables
Excel.Application xlApp = new Excel.Application();
Excel.Workbooks xlWorkbooks = xlApp.Workbooks;
Excel.Workbook xlWorkbook = xlWorkbooks.Add(Missing.Value);
Excel.Worksheet xlWorksheet = (Excel.Worksheet)xlWorkbook.Worksheets.get_Item(1);
//Create worksheet
xlWorkbook.Close(false, Missing.Value, Missing.Value);
xlWorkbooks.Close();
xlApp.Quit();
Marshal.FinalReleaseComObject(xlWorksheet);
Marshal.FinalReleaseComObject(xlWorkbook);
Marshal.FinalReleaseComObject(xlWorkbooks);
Marshal.FinalReleaseComObject(xlApp);
xlWorksheet = null;
xlWorkbook = null;
xlWorkbooks = null;
xlApp = null;
GC.Collect();
This article from Microsoft has some good information regarding this issue.
I am a total COM amateur, used it for a minor thing in one project quite a long time ago, but here's a snippet I used there. I probably found it somewhere online, don't remember. In any case, I paste it its full glory ;)
public static class ComBlackBox
{
public static void ReleaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (ArgumentException ex)
{
obj = null;
MessageBox.Show("Unable to release the Object " + ex.Message);
}
finally
{
GC.Collect();
}
}
}
I'm unable to try it out now, but it probably worked (I honestly don't remember any details). Maybe it will help you out. Feel free to point out any obvious problems with this code, I really am far from being COM-literate ;)
This is how I got around this problem:
// Store the Excel processes before opening.
Process[] processesBefore = Process.GetProcessesByName("excel");
// Open the file in Excel.
Application excelApplication = new Application();
Workbook excelWorkbook = excelApplication.Workbooks.Open(Filename);
// Get Excel processes after opening the file.
Process[] processesAfter = Process.GetProcessesByName("excel");
// Now find the process id that was created, and store it.
int processID = 0;
foreach (Process process in processesAfter)
{
if (!processesBefore.Select(p => p.Id).Contains(process.Id))
{
processID = process.Id;
}
}
// Do the Excel stuff
// Now close the file with the COM object.
excelWorkbook.Close();
excelApplication.Workbooks.Close();
excelApplication.Quit();
// And now kill the process.
if (processID != 0)
{
Process process = Process.GetProcessById(processID);
process.Kill();
}