Cannot close Excel.exe after Interop process
Simple rule: avoid using double-dot-calling expressions, such as this:
var workbook = excel.Workbooks.Open(/*params*/)
...because in this way you create RCW objects not only for workbook
, but for Workbooks
, and you should release it too (which is not possible if a reference to the object is not maintained).
So, the right way will be:
var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)
//business logic here
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
Closing Excel Application Process in C# after Data Access
Try this:
excelBook.Close(0);
excelApp.Quit();
When closing the work-book, you have three optional parameters:
Workbook.close SaveChanges, filename, routeworkbook
Workbook.Close(false)
or if you are doing late binding, it sometimes is easier to use zeroWorkbook.Close(0)
That is how I've done it when automating closing of workbooks.
Also I went and looked up the documentation for it, and found it here:
Excel Workbook Close
How to close excel in C#?
This is something I've played around with a lot while using SSIS Script Tasks to refresh Excel files.
I've read mixed things about using Marshal.ReleaseComObject
, but I've also found that it isn't necessary. For me, the ultimate solution was found to be the following:
using xl = Microsoft.Office.Interop.Excel;
...
public void Main()
{
DoExcelWork();
GC.Collect();
GC.WaitForPendingFinalizers();
}
private void DoExcelWork()
{
xl.Application app = null;
xl.Workbooks books = null;
xl.Workbook book = null;
try
{
app = new xl.Application() { DisplayAlerts = false };
books = app.Workbooks;
book = books.Open("file path goes here");
book.RefreshAll();
// this is where you would do your Excel work
app.DisplayAlerts = false; // This is for reinforcement; the setting has been known to reset itself after a period of time has passed.
book.SaveAs("save path goes here");
app.DisplayAlerts = true;
book.Close();
app.Quit();
}
catch
{
if (book != null) book.Close(SaveChanges: false);
if (app != null) app.Quit();
}
}
I'm not sure how your application is laid out, but when using SSIS I found it was necessary to call GC.Collect
outside of the scope where the Excel Interop objects were declared in order to avoid having the Excel instances left open on some occasions, hence the two methods.
You should be able to adapt this code to suit your requirements.
Cannot close Excel.exe after killing it from Task Manager
If you show us your complete usage of your excel object, I bet you will find you are breaking the 2 dot rule. Sounds absurd I know, but I think you are inadvertently creating instances of objects that cannot be disposed by Excel or the GC and then it cannot close properly. (Called Runtime Callable Wrappers)
When you use - say - this code:
xlWorkBook = xlApp.Workbooks.Add
...you are actually causing the problem. You need to create a variable pointing to Workbooks
and use that directly and dispose of it.
Example from the link:
Dim xlApp As New Excel.Application
Dim xlWorkBooks As Excel.Workbooks = xlApp.Workbooks
Dim xlWorkBook As Excel.Workbook = xlWorkBooks.Add()
....all your code, then
xlApp.Quit()
If Not xlWorkBook Is Nothing Then
Marshal.FinalReleaseComObject (xlWorkBook)
xlWorkBook = Nothing
End If
If Not xlWorkBooks Is Nothing Then
Marshal.FinalReleaseComObject (xlWorkBooks)
xlWorkBooks = Nothing
End If
If Not xlApp Is Nothing Then
Marshal.FinalReleaseComObject (xlApp)
xlApp = Nothing
End If
xlApp.Quit()
See here for details:
http://www.siddharthrout.com/2012/08/06/vb-net-two-dot-rule-when-working-with-office-applications-2/
Excel interop COM doesn't close
Ended up killing the processes, that was the only thing that worked.
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if (processID == 0) return false;
try
{
Process.GetProcessById((int)processID).Kill();
}
catch (ArgumentException)
{
return false;
}
catch (Exception ex)
{
return false;
}
return true;
}
static void ParseFile(string file)
{
try
{
log("parsing:" + file);
Excel.Application excel = new Excel.Application();
Excel.Workbook wb = excel.Workbooks.Open(file);
Excel.Worksheet ws = wb.Worksheets[1];
//do some stuff here
wb.Close(false);
int hWnd = excel.Application.Hwnd;
excel.Quit();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(ws);
Marshal.FinalReleaseComObject(wb);
Marshal.FinalReleaseComObject(excel);
excel = null;
ws = null;
wb = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
TryKillProcessByMainWindowHwnd(hWnd);
}
catch (Exception ex)
{
log(ex.Message);
}
}
Application not quitting after calling quit
Just Calling .Quit()
will not remove the Application from memory. It is very important to close the objects after you are done with your coding. This ensures that all objects are released properly and nothing remains in the memory.
See this example
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
'~~> Define your Excel Objects
Dim xlApp As New Excel.Application
Dim xlWorkBook As Excel.Workbook
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'~~> Add a New Workbook
xlWorkBook = xlApp.Workbooks.Add
'~~> Display Excel
xlApp.Visible = True
'~~> Do some stuff Here
'~~> Save the file
xlWorkBook.SaveAs(Filename:="C:\Tutorial\SampleNew.xlsx", FileFormat:=51)
'~~> Close the File
xlWorkBook.Close()
'~~> Quit the Excel Application
xlApp.Quit()
'~~> Clean Up
releaseObject (xlApp)
releaseObject (xlWorkBook)
End Sub
'~~> Release the objects
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject (obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Me.Close()
End Sub
End Class
Also worth mentioning is the 2 DOT Rule.
If you love automating Excel from VB.Net then you might also want to have a look at this link.
FOLLOWUP
The problem is the 2 DOT Rule as I mentioned above. When you use the 2 DOT Rule (Ex: Excel.XlBordersIndex.xlDiagonalDown
) then you have to do the Garbage Collection by using GC.Collect()
. So All you need to do is add this part
Finally
GC.Collect()
in the Private Sub ReleaseObject(ByVal obj As Object)
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
Loop While intRel > 0
MsgBox("Final Released obj # " & intRel)
Catch ex As Exception
MsgBox("Error releasing object" & ex.ToString)
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
FINAL CODE
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim xlApp As New excel.Application
Dim xlWorkBook As excel.Workbook
Dim xlWorkSheet As excel.Worksheet
Dim xlRange As excel.Range
'Dim misValue As Object = System.Reflection.Missing.Value
xlWorkBook = xlApp.Workbooks.Add
xlWorkSheet = DirectCast(xlWorkBook.Sheets("sheet1"), excel.Worksheet)
xlApp.Visible = True
Dim headers = (From ch In DataGridView1.Columns _
Let header = DirectCast(DirectCast(ch, DataGridViewColumn).HeaderCell, DataGridViewColumnHeaderCell) _
Select header.Value).ToArray()
Dim headerText() As String = Array.ConvertAll(headers, Function(v) v.ToString)
Dim items() = (From r In DataGridView1.Rows _
Let row = DirectCast(r, DataGridViewRow) _
Where Not row.IsNewRow _
Select (From cell In row.Cells _
Let c = DirectCast(cell, DataGridViewCell) _
Select c.Value).ToArray()).ToArray()
Dim table As String = String.Join(vbTab, headerText) & Environment.NewLine
For Each a In items
Dim t() As String = Array.ConvertAll(a, Function(v) v.ToString)
table &= String.Join(vbTab, t) & Environment.NewLine
Next
table = table.TrimEnd(CChar(Environment.NewLine))
Clipboard.SetText(table)
Dim alphabet() As Char = "abcdefghijklmnopqrstuvwxyz".ToUpper.ToCharArray
xlRange = xlWorkSheet.Range("B2:" & alphabet(headerText.Length) & (items.Length + 2).ToString)
xlRange.Select()
xlWorkSheet.Paste()
xlRange.Borders(excel.XlBordersIndex.xlDiagonalDown).LineStyle = excel.XlLineStyle.xlLineStyleNone
xlRange.Borders(excel.XlBordersIndex.xlDiagonalUp).LineStyle = excel.XlLineStyle.xlLineStyleNone
With xlRange.Borders(excel.XlBordersIndex.xlEdgeLeft)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlMedium
End With
With xlRange.Borders(excel.XlBordersIndex.xlEdgeTop)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlMedium
End With
With xlRange.Borders(excel.XlBordersIndex.xlEdgeBottom)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlMedium
End With
With xlRange.Borders(excel.XlBordersIndex.xlEdgeRight)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlMedium
End With
With xlRange.Borders(excel.XlBordersIndex.xlInsideVertical)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlThin
End With
With xlRange.Borders(excel.XlBordersIndex.xlInsideHorizontal)
.LineStyle = excel.XlLineStyle.xlContinuous
.ColorIndex = 1 'black
.TintAndShade = 0
.Weight = excel.XlBorderWeight.xlThin
End With
xlWorkBook.SaveAs(Filename:="C:\Users\Siddharth Rout\Desktop\Word1.xls", FileFormat:=56)
xlWorkBook.Close()
xlApp.Quit()
ReleaseObject(xlRange)
ReleaseObject(xlWorkSheet)
ReleaseObject(xlWorkBook)
ReleaseObject(xlApp)
End Sub
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
Loop While intRel > 0
MsgBox("Final Released obj # " & intRel)
Catch ex As Exception
MsgBox("Error releasing object" & ex.ToString)
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
Related Topics
How to Bring My Application Window to the Front
How to Obfuscate My C# Code, So It Can't Be Deobfuscated So Easily
C#: Difference Between "System.Object" and "Object"
Windows.Ui.Notifications Is Missing
Differencebetween Int, Int16, Int32 and Int64
How to Find a Specific Element in a List<T>
Entity Framework 6 Transaction Rollback
Instantiate an Object with a Runtime-Determined Type
Can't Find System.Windows.Media Namespace
What Is the Real Overhead of Try/Catch in C#
Configure JSON.Net to Ignore Datacontract/Datamember Attributes
Displaying Arabic Characters in C# Console Application
C# Naming Convention for Constants
What Is the Correct Performance Counter to Get CPU and Memory Usage of a Process