Able to close all excel processes except the first instance - c#

I am trying to close excel process in my winform application. I have gone through lots of posts on SO and other sites and this is what I am doing right now:
private void lsvSelectedQ_SelectedIndexChanged(object sender, EventArgs e)
{
FillSelectedItems();
}
private void FillSelectedItems()
{
string filepath = string.Empty;
string reportname = lblreportname.Text;
filepath = Application.StartupPath + "\\StandardReports\\" + reportname + ".xls";
Microsoft.Office.Interop.Excel.Application xlApp;
Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
object misValue = System.Reflection.Missing.Value;
xlApp = new Microsoft.Office.Interop.Excel.Application();
xlWorkBook = xlApp.Workbooks.Open(filepath, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
List<string> WorksheetList = new List<string>();
xlWorkSheet = (Microsoft.Office.Interop.Excel.Worksheet)xlWorkBook.Worksheets.get_Item("Config");
Microsoft.Office.Interop.Excel.Range objRange = null;
objRange = (Microsoft.Office.Interop.Excel.Range)xlWorkSheet.Cells[6, 2];
if (objRange.Value != null)
{
int intSheet = Convert.ToInt32(objRange.Value);
for (int i = 0; i < intSheet; i++)
{
objRange = (Microsoft.Office.Interop.Excel.Range)xlWorkSheet.Cells[6, 3+i ];
if (objRange.Value != null)
{
lsvSelectedQ.Items.Add(Convert.ToString(objRange.Value));
}
}
}
ReleaseMyExcelsObjects(xlApp, xlWorkBook, xlWorkSheet);
}
In the above code I am using ReleaseMyExcelsObjects method to get rid of running excel process in the taskbar.
private void ReleaseMyExcelsObjects(Microsoft.Office.Interop.Excel.Application xlApp, Microsoft.Office.Interop.Excel.Workbook xlWorkBook, Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet)
{
xlApp.DisplayAlerts = false;
xlWorkBook.Save();
xlWorkBook.Close();
xlApp.DisplayAlerts = false;
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkSheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
xlWorkSheet = null;
xlWorkBook = null;
xlApp = null;
GC.Collect();
}
As you can see I open an excel on a SelectedIndexChanged and I am also trying to close the process through ReleaseMyExcelsObjects() method and it works except for the first excel process that is generated. I mean when the event is fired the excel process is started and ReleaseMyExcelsObjects() does not close it however the second time SelectedIndexChanged is fired, another excel process is started. This time ReleaseMyExcelsObjects() closes this second excel process. But the first excel process which was started when theSelectedIndexChanged event was fired for the first time never gets closed.
EDIT:
I have posted an answer myself which is getting the job done. But I am going to keep this question open if in case someone comes up with better solution.

I have found a work around for this:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
public static Process GetExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
int id;
GetWindowThreadProcessId(excelApp.Hwnd, out id);
return Process.GetProcessById(id);
}
Use it as classobj.GetExcelProcess(xlApp).Kill();
Taken from : https://stackoverflow.com/a/15556843/2064292

from my understanding, the ReleaseComObject closes the Excel Application when your C# application exits. Thus, a new Excel processes will show up in your taskbar every time you call FillSelectedItems() which won't close until you exit your C# application.
EDIT: On a side note, I recommend using try, catch, finally when handling the excel interop library, mainly due to the fact that if the application runs into an exception, the program will not exit normally, and as stated before it will result on the excel process remaining in the taskbar (and when you manually open said file on excel it will tell you the last recovered version blablabla)
string filepath = string.Empty;
string reportname = lblreportname.Text;
filepath = Application.StartupPath + "\\StandardReports\\" + reportname + ".xls";
Microsoft.Office.Interop.Excel.Application xlApp;
Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
object misValue = System.Reflection.Missing.Value;
xlApp = new Microsoft.Office.Interop.Excel.Application();
xlWorkBook = xlApp.Workbooks.Open(filepath, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
List<string> WorksheetList = new List<string>();
try
{
//Your code
}
catch (Exception ex) { MessageBox.Show(string.Format("Error: {0}", ex.Message)); }
finally
{
xlWorkBook.Close(false, filepath, null);
Marshal.ReleaseComObject(xlWorkBook);
}
Honestly, if you are annoyed by this, I would recommend using EPPlus or ExcelDataReader(this one only for reading the excel spreadsheets) as alternative libraries. Otherwise, No matter how many releaseComObjects or garbagecollectors you add, I believe you wont get rid of this issue.
EDIT 2: Of course, another way to go around this is to search for an Excel process ID and kill it. The reason I did not recommend this, is because in the event that the user who's running this application already has another excel process running on his computer, you could end up killing that instance.

I have a solution for closing the Excel process. Instead of going thru the pains of releasing your objects, you can kill that specific excel process (if you have multiple of them open).
The code is:
using System;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace YourNameSpace
{
public class MicrosoftApplications
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
public class Excel
{
public Excel()
{
Application = new Microsoft.Office.Interop.Excel.Application();
RegisterExitEvent();
}
public Microsoft.Office.Interop.Excel.Application Application;
private void RegisterExitEvent()
{
Application.WindowDeactivate -= XlApp_WindowDeactivate;
Application.WindowDeactivate += XlApp_WindowDeactivate;
}
private void XlApp_WindowDeactivate(Workbook Wb, Window Wn)
{
Kill();
}
public void Kill()
{
int pid = 0;
GetWindowThreadProcessId(Application.Hwnd, out pid);
if (pid > 0)
{
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(pid);
p.Kill();
}
Application = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
}
And you can call it by: YourNameSpace.MicrosoftApplications.Excel xlApp = new YourNameSpace.MicrosoftApplications.Excel();
Do whatever you need to do by calling xlApp.Application.whatever instead of xlApp.whatever and if the user exits the excel window(s) it will kill the process(es) that were used in the code. If you want to just generate a report behind the scenes but not display the form, then simply call xlApp.Kill(); to end that specific process.

Related

C# Prevent users from losing data in excel interop

I am writing an application in C# that changes data in an excel worksheet. VBA isn't an option because the version of office that is installed on the clients desktop is Microsoft Office 2010 Starter Edition which doesn't support VBA (none of the starter editions do). The application is using the excel interop library.
When I start the application it checks to see if the excel workbook that is to be modified is open and if it is open it notifies the user and then quits. This part is working as expected. The check isn't working if the user opens the excel file for some reason after starting the application and then trying to save their work from inside the application. In that case any modifications from the application are lost without any error notification. If you need to see more of the code to answer the entire project is in GitHub.
I've tried changing CheckExcelWorkBookOpen from a static class to a class that gets instantiated every time it is used, just in case the list of open workbooks was being stored in the excel interop library, this did not help.
The code that works in the application start up is:
CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
testOpen.TestAndThrowIfOpen(Preferences.ExcelWorkBookFullFileSpec);
The code is also called any time the application attempts to open the file either for input or output, this doesn't work:
private void StartExcelOpenWorkbook()
{
if (xlApp != null)
{
return;
}
CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
testOpen.TestAndThrowIfOpen(WorkbookName);
xlApp = new Excel.Application();
xlApp.Visible = false;
xlApp.DisplayAlerts = false;
xlWorkbook = xlApp.Workbooks.Open(WorkbookName);
}
Current Code
using System;
using Excel = Microsoft.Office.Interop.Excel;
namespace TenantRosterAutomation
{
public class CheckExcelWorkBookOpen
{
// Check if there is any instance of excel open using the workbook.
public static bool IsOpen(string workBook)
{
Excel.Application TestOnly = null;
bool isOpened = true;
// There are 2 possible exceptions here, GetActiveObject will throw
// an exception if no instance of excel is running, and
// workbooks.get_Item throws an exception if the sheetname isn't found.
// Both of these exceptions indicate that the workbook isn't open.
try
{
TestOnly = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
int lastSlash = workBook.LastIndexOf('\\');
string fileNameOnly = workBook.Substring(lastSlash + 1);
TestOnly.Workbooks.get_Item(fileNameOnly);
TestOnly = null;
}
catch (Exception)
{
isOpened = false;
if (TestOnly != null)
{
TestOnly = null;
}
}
return isOpened;
}
// Common error message to use when the excel file is op in another app.
public string ReportOpen()
{
string alreadyOpen = "The excel workbook " +
Globals.Preferences.ExcelWorkBookFullFileSpec +
" is alread open in another application. \n" +
"Please save your changes in the other application and close the " +
"workbook and then try this operation again or restart this application.";
return alreadyOpen;
}
public void TestAndThrowIfOpen(string workBook)
{
if (IsOpen(workBook))
{
AlreadyOpenInExcelException alreadOpen =
new AlreadyOpenInExcelException(ReportOpen());
throw alreadOpen;
}
}
}
}
This code is now included in a question on code review.
I got the above code to work by ensuring that any excel process started by the application was killed after the task was complete. The following code is added to my ExcelInterface module. The Dispose(bool) function already existed but did not kill the process:
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (xlWorkbook != null)
{
xlWorkbook.Close();
xlWorkbook = null;
}
if (xlApp != null)
{
xlApp.Quit();
xlApp = null;
Process xlProcess = Process.GetProcessById(ExcelProcessId);
if (xlProcess != null)
{
xlProcess.Kill();
}
}
}
disposed = true;
}
}
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
private int GetExcelProcessID(Excel.Application excelApp)
{
int processId;
GetWindowThreadProcessId(excelApp.Hwnd, out processId);
return processId;
}
private void StartExcelOpenWorkbook()
{
if (xlApp != null)
{
return;
}
CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
testOpen.TestAndThrowIfOpen(WorkbookName);
xlApp = new Excel.Application();
xlApp.Visible = false;
xlApp.DisplayAlerts = false;
xlWorkbook = xlApp.Workbooks.Open(WorkbookName);
ExcelProcessId = GetExcelProcessID(xlApp);
}

Excel does not Quit C#

I am writing this particular code in C# to write to an excel file.
public partial class WriteExcelForm : Form
{
public WriteExcelForm()
{
InitializeComponent();
}
private void writeExcelButton_Click(object sender, EventArgs e)
{
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
if (xlApp == null)
{
MessageBox.Show("Excel is not installed!!!");
return;
}
Excel.Workbooks xlWorkBooks = xlApp.Workbooks;
Excel.Workbook xlWorkBook = xlWorkBooks.Add(Type.Missing);
Excel.Worksheet xlWorkSheet = xlWorkBook.ActiveSheet;
xlWorkSheet.Name = "sample";
Excel.Range range1 = (Excel.Range)xlWorkSheet.Cells[1, 1];
range1.Value = "dhiraj";
Excel.Range range2 = xlWorkSheet.Range["A2"];
range2.Value = "dhiraj";
xlWorkBook.SaveAs("C:\\output.xlsx");
//Properly closing the excel app
GC.Collect();
GC.WaitForPendingFinalizers();
xlWorkBook.Close(false, Type.Missing, Type.Missing);
xlApp.Quit();
Marshal.FinalReleaseComObject(range1);
Marshal.FinalReleaseComObject(range2);
Marshal.FinalReleaseComObject(xlWorkSheet);
Marshal.FinalReleaseComObject(xlWorkBook);
Marshal.FinalReleaseComObject(xlWorkBooks);
Marshal.FinalReleaseComObject(xlApp);
}
}
If I run this code, the excel.exe does not quit, but keeps on hanging around in the background.
However, if I comment out this particular line
Excel.Range range1 = (Excel.Range)xlWorkSheet.Cells[1, 1];
range1.Value = "dhiraj";
the excel.exe quits elegantly.
What am I missing here?
EDIT:
I have solved my issue. Posting my findings as answer.
P.S: Do not know why I was down voted, I did research a lot before posting this question.
So, I continued my research after asking the question, and I found this particular link
How do I properly clean up Excel interop objects?
Now in this particular link, three answers which when put together helped me out.
First answer is this particular one https://stackoverflow.com/a/158752/2241802
It says to avoid using "Never use two dots with COM objects".
Second answer is this one https://stackoverflow.com/a/159419/2241802
This answer refers to how to use excel object practically and it is very important to follow this, since I will not be the only developer working on my piece of code.
The third answer https://stackoverflow.com/a/1893653/2241802
talks about the how Garbage Collector behavior differs in Release mode and Debug mode.
So that was my problem, when i ran the code pasted in the question in Release mode, it worked fine and the excel.exe quit gracefully.
However, to make it work in the Debug mode I took the suggestion of the Thrid Answer link above and created a function which implemented the excel writing stuff.
Find below code
protected void Button3_Click(object sender, EventArgs e)
{
int randomvalue = new Random().Next(10);
try
{
filename = Server.MapPath("~/Reports/Tro_Reports" + randomvalue + ".xlsx");
using (var getReportCollection = new DataSet1())
{
using (var tableCollection = getReportCollection.Tables["SheetNames"])
{
var excelApplication = new Microsoft.Office.Interop.Excel.Application();
try
{
var wb = excelApplication.Workbooks.Add();
var collection = new Microsoft.Office.Interop.Excel.Worksheet[20];
for (var i = 0; i < tableCollection.Columns.Count; i++)
{
collection[i] = wb.Worksheets.Add();
collection[i].Name = tableCollection.Columns[i].ToString();
}
var thisWorksheet = collection[2];
var thisRange = thisWorksheet.Range["A1"];
thisRange.Value = "Event Summary Report";
wb.SaveAs(filename);
wb.Close();
}
finally
{
Marshal.ReleaseComObject(excelApplication);
}
}
}
}
catch (ExternalException ex)
{
}
}
The above code snippet will work fine to close your excel sheet.

Closing an Excel Workbook

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);

Safely disposing Excel interop objects in C#?

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.

Excel process remains open after interop; traditional method not working

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();
}

Categories