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.
Related
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);
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();
}
}
This question already has answers here:
How do I properly clean up Excel interop objects?
(43 answers)
Closed 9 years ago.
I've got this C# program that never closes the Excel process. Basically it finds the number of instances a string appears in a range in Excel. I've tried all kinds of things, but it's not working. There is a Form that is calling this method, but that shouldn't change why the process isn't closing. I've looks at suggestions by Hans Passant, but none are working.
EDIT: I tried the things mentioned and it still won't close. Here's my updated code.
EDIT: Tried the whole Process.Kill() and it works, but it seems like a bit of a hack for something that should just work.
public class CompareHelper
{
// Define Variables
Excel.Application excelApp = null;
Excel.Workbooks wkbks = null;
Excel.Workbook wkbk = null;
Excel.Worksheet wksht = null;
Dictionary<String, int> map = new Dictionary<String, int>();
// Compare columns
public void GetCounts(string startrow, string endrow, string columnsin, System.Windows.Forms.TextBox results, string excelFile)
{
results.Text = "";
try
{
// Create an instance of Microsoft Excel and make it invisible
excelApp = new Excel.Application();
excelApp.Visible = false;
// open a Workbook and get the active Worksheet
wkbks = excelApp.Workbooks;
wkbk = wkbks.Open(excelFile, Type.Missing, true);
wksht = wkbk.ActiveSheet;
...
}
catch
{
throw;
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (wksht != null)
{
//wksht.Delete();
Marshal.FinalReleaseComObject(wksht);
wksht = null;
}
if (wkbks != null)
{
//wkbks.Close();
Marshal.FinalReleaseComObject(wkbks);
wkbks = null;
}
if (wkbk != null)
{
excelApp.DisplayAlerts = false;
wkbk.Close(false, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(wkbk);
wkbk = null;
}
if (excelApp != null)
{
excelApp.Quit();
Marshal.FinalReleaseComObject(excelApp);
excelApp = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
/*
Process[] processes = Process.GetProcessesByName("EXCEL");
foreach (Process p in processes)
{
p.Kill();
}
*/
}
}
}
Here is an interesting knowledge base on the subject of office apps staying open after a .NET app disconnects from them.
Office application does not quit after automation from Visual Studio .NET client
The code examples are all in the link (vb.net sorry). Basically it shows you how to correctly setup and tear down the office app so that it closes when you're finished with it.
System.Runtime.InteropServices.Marshal.FinalReleaseComObject is where the magic happens.
EDIT: You need to call the FinalReleaseComObject for each excel object that you've created.
if (excelWorkSheet1 != null)
{
Marshal.FinalReleaseComObject(excelWorkSheet1);
excelWorkSheet1 = null;
}
if (excelWorkbook != null)
{
Marshal.FinalReleaseComObject(excelWorkbook);
excelWorkbook = null;
}
if (excelApp != null)
{
Marshal.FinalReleaseComObject(excelApp);
excelApp = null;
}
I finally got it to close. You need to add a variable for the Workbooks collection, and then use the FinalReleaseComObject as stated in the other answers. I guess every possible Excel COM object that you use must be disposed this way.
try
{
// Create an instance of Microsoft Excel and make it invisible
excelApp = new Excel.Application();
excelApp.DisplayAlerts = false;
excelApp.Visible = false;
// open a Workbook and get the active Worksheet
excelWorkbooks = excelApp.Workbooks;
excelWorkbook = excelWorkbooks.Open(excelFile, Type.Missing, true);
excelWorkSheet1 = excelWorkbook.ActiveSheet;
}
catch
{
throw;
}
finally
{
NAR( excelWorkSheet1 );
excelWorkbook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
NAR(excelWorkbook);
NAR(excelWorkbooks);
excelApp.Quit();
NAR(excelApp);
}
}
private void NAR(object o)
{
try
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject( o );
}
catch { }
finally
{
o = null;
}
}
DotNet only release the COM object after all the handles have been released. What I do is comment everything out, and then add back a portion. See if it release Excel. If it did not follow the following rules. When it release, add more code until it does not release again.
1) When you create your Excel variables, set all the values to null (this avoid not initiated errors)
2) Do not reuse variables without releasing it first Marshal.FinalReleaseComObject
3) Do not double dot (a.b = z). dotNet create a temporary variable, which will not get released.
c = a.b;
c = z;
Marshal.FinalReleaseComObject(c);
4) Release ALL excel variables. The quicker the better.
5) Set it back to NULL.
Set culture to "en-US". There is a bug that crash Excel with some cultures. This ensure it won't.
Here is an idea of how your code should be structured:
thisThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
InteropExcel.Application excelApp = null;
InteropExcel.Workbooks wkbks = null;
InteropExcel.Workbook wkbk = null;
try
{
excelApp = new InteropExcel.Application();
wkbks = excelApp.Workbooks;
wkbk = wkbks.Open(fileName);
...
}
catch (Exception ex)
{
}
if (wkbk != null)
{
excelApp.DisplayAlerts = false;
wkbk.Close(false);
Marshal.FinalReleaseComObject(wkbk);
wkbk = null;
}
if (wkbks != null)
{
wkbks.Close();
Marshal.FinalReleaseComObject(wkbks);
wkbks = null;
}
if (excelApp != null)
{
// Close Excel.
excelApp.Quit();
Marshal.FinalReleaseComObject(excelApp);
excelApp = null;
}
// Change culture back from en-us to the original culture.
thisThread.CurrentCulture = originalCulture;
}
I have a bit of code that opens an xls workbook;
Excel.Workbooks workBooks;
workBooks = excelApp.Workbooks;
workbook = workBooks.Open(sourceFilePath + sourceFileName + ".xls");
I then get the work sheet;
worksheets = workbook.Worksheets;
worksheet = worksheets.get_Item("Standard");
I then save the file as a csv;
worksheet.SaveAs(sourceFilePath + sourceFileName + ".csv", Excel.XlFileFormat.xlCSVWindows, Type.Missing, Type.Missing, false, false, Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing);
Then I try to close the workbook;
Marshal.FinalReleaseComObject(worksheet);
Marshal.FinalReleaseComObject(worksheets);
workbook.Close();
Marshal.FinalReleaseComObject(workbook);
However, every time i get to the line workbook.Close(), the system stops.
If I do not do the SaveAs then the workbook closes just fine.
How do I close a workbook?
edit
Looking at Task Manager shows me that Excel.exe is still running. Closing it will produce an error in my code.
edit 2
I have already seen the referenced SO post and it did not solve the issue.
Here is the solution
first:
using EXCEL = Microsoft.Office.Interop.Excel;
and then, path is where your excel locates.
EXCEL.Application excel = new EXCEL.Application();
try
{
EXCEL.Workbook book = excel.Application.Workbooks.Open(path);
EXCEL.Worksheet sheet = book.Worksheets[1];
// yout operation
}
catch (Exception ex) { MessageBox.Show("readExcel:" + ex.Message); }
finally
{
KillExcel(excel);
System.Threading.Thread.Sleep(100);
}
[DllImport("User32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int ProcessId);
private static void KillExcel(EXCEL.Application theApp)
{
int id = 0;
IntPtr intptr = new IntPtr(theApp.Hwnd);
System.Diagnostics.Process p = null;
try
{
GetWindowThreadProcessId(intptr, out id);
p = System.Diagnostics.Process.GetProcessById(id);
if (p != null)
{
p.Kill();
p.Dispose();
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("KillExcel:" + ex.Message);
}
}
Why not combine the 2. This will take care of any problems with closing before saving is complete.
There is an option in the Close method to save the file.
workbook.Close(true, fileName, Missing.Value);
Also if the file is saving correctly, and your problem is purely because the excel.exe process is still running, it could be because you didn't close and release EVERYTHING needed. I have had this before and developed a more complete close down routine. My code for shutting down an excel file is:
book.Close(true, fileName, Missing.Value); //close and save individual book
allBooks.Close(); //close all books
excel.Quit();
Marshal.ReleaseComObject(allCells); //any used range objects
Marshal.ReleaseComObject(sheet);
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(book);
Marshal.ReleaseComObject(allBooks);
Marshal.ReleaseComObject(excel);
This works 100% of the time for me.
Have you considered the fact that the system might still be in the process of saving the file when you attempt to close it? I'm just saying, to be sure add a delay(Thread.Sleep(1000) in C# for example) before the close to see if this is the problem.
This question keeps popping up see:
How to properly clean up Excel interop objects in C#
You need to call System.Runtime.InteropServices.Marshal.ReleaseComObject() on every excel object you use, even invisible ones, e.g.:
var worksheet = excelApp.Worksheets.Open()
There are two objects here:
1. The obvious 'Worksheet' opened with Open()
2. The "invisible" collection 'Worksheets'.
Both of them need to be released (so you better keep a reference for Worksheets):
var wkCol = excelApp.Worksheets;
var worksheet = wkCol.Open();
EXCEL.Application excel = new EXCEL.Application();
try
{
EXCEL.Workbook book = excel.Application.Workbooks.Open(path);
EXCEL.Worksheet sheet = book.Worksheets[1];
// yout operation
}
catch (Exception ex) { MessageBox.Show("readExcel:" + ex.Message); }
finally
{
KillExcel(excel);
System.Threading.Thread.Sleep(100);
}
[DllImport("User32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int ProcessId);
private static void KillExcel(EXCEL.Application theApp)
{
int id = 0;
IntPtr intptr = new IntPtr(theApp.Hwnd);
System.Diagnostics.Process p = null;
try
{
GetWindowThreadProcessId(intptr, out id);
p = System.Diagnostics.Process.GetProcessById(id);
if (p != null)
{
p.Kill();
p.Dispose();
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("KillExcel:" + ex.Message);
}
}
Thank you!!!!
This work for me. EXCEL.EXE will be kill after close you program.
Excel.Application objExcel;
Excel._Workbook objBook;
Excel.Workbooks objBooks;
Excel._Worksheet objSheet;
Excel.Sheets objSheets;
objExcel = new Excel.Application();
objBooks = objExcel.Workbooks;
objBook = objExcel.Workbooks.Open(Application.StartupPath+#"/"+"template.xls");
objSheets = objBook.Worksheets;
objSheet = (Excel._Worksheet)objBook.ActiveSheet;
//....you code modify excel book
objBook.Close(true, objBook, Missing.Value);
objExcel.Quit();
Marshal.ReleaseComObject(objSheet);
Marshal.ReleaseComObject(objSheets);
Marshal.ReleaseComObject(objBook);
Marshal.ReleaseComObject(objBooks);
Marshal.ReleaseComObject(objExcel);
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();
}