Properly Releasing Excel From Memory [duplicate] - c#
I'm using the Excel interop in C# (ApplicationClass) and have placed the following code in my finally clause:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Although this kind of works, the Excel.exe process is still in the background even after I close Excel. It is only released once my application is manually closed.
What am I doing wrong, or is there an alternative to ensure interop objects are properly disposed of?
Excel does not quit because your application is still holding references to COM objects.
I guess you're invoking at least one member of a COM object without assigning it to a variable.
For me it was the excelApp.Worksheets object which I directly used without assigning it to a variable:
Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);
I didn't know that internally C# created a wrapper for the Worksheets COM object which didn't get released by my code (because I wasn't aware of it) and was the cause why Excel was not unloaded.
I found the solution to my problem on this page, which also has a nice rule for the usage of COM objects in C#:
Never use two dots with COM objects.
So with this knowledge the right way of doing the above is:
Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
POST MORTEM UPDATE:
I want every reader to read this answer by Hans Passant very carefully as it explains the trap I and lots of other developers stumbled into. When I wrote this answer years ago I didn't know about the effect the debugger has to the garbage collector and drew the wrong conclusions. I keep my answer unaltered for the sake of history but please read this link and don't go the way of "the two dots": Understanding garbage collection in .NET and Clean up Excel Interop Objects with IDisposable
You can actually release your Excel Application object cleanly, but you do have to take care.
The advice to maintain a named reference for absolutely every COM object you access and then explicitly release it via Marshal.FinalReleaseComObject() is correct in theory, but, unfortunately, very difficult to manage in practice. If one ever slips anywhere and uses "two dots", or iterates cells via a for each loop, or any other similar kind of command, then you'll have unreferenced COM objects and risk a hang. In this case, there would be no way to find the cause in the code; you would have to review all your code by eye and hopefully find the cause, a task that could be nearly impossible for a large project.
The good news is that you do not actually have to maintain a named variable reference to every COM object you use. Instead, call GC.Collect() and then GC.WaitForPendingFinalizers() to release all the (usually minor) objects to which you do not hold a reference, and then explicitly release the objects to which you do hold a named variable reference.
You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object.
For example, assuming that you had a Range object variable named xlRng, a Worksheet variable named xlSheet, a Workbook variable named xlBook and an Excel Application variable named xlApp, then your cleanup code could look something like the following:
// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);
xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);
xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);
In most code examples you'll see for cleaning up COM objects from .NET, the GC.Collect() and GC.WaitForPendingFinalizers() calls are made TWICE as in:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
This should not be required, however, unless you are using Visual Studio Tools for Office (VSTO), which uses finalizers that cause an entire graph of objects to be promoted in the finalization queue. Such objects would not be released until the next garbage collection. However, if you are not using VSTO, you should be able to call GC.Collect() and GC.WaitForPendingFinalizers() just once.
I know that explicitly calling GC.Collect() is a no-no (and certainly doing it twice sounds very painful), but there is no way around it, to be honest. Through normal operations you will generate hidden objects to which you hold no reference that you, therefore, cannot release through any other means other than calling GC.Collect().
This is a complex topic, but this really is all there is to it. Once you establish this template for your cleanup procedure you can code normally, without the need for wrappers, etc. :-)
I have a tutorial on this here:
Automating Office Programs with VB.Net / COM Interop
It's written for VB.NET, but don't be put off by that, the principles are exactly the same as when using C#.
Preface: my answer contains two solutions, so be careful when reading and don't miss anything.
There are different ways and advice of how to make Excel instance unload, such as:
Releasing EVERY com object explicitly
with Marshal.FinalReleaseComObject()
(not forgetting about implicitly
created com-objects). To release
every created com object, you may use
the rule of 2 dots mentioned here:
How do I properly clean up Excel interop objects?
Calling GC.Collect() and
GC.WaitForPendingFinalizers() to make
CLR release unused com-objects * (Actually, it works, see my second solution for details)
Checking if com-server-application
maybe shows a message box waiting for
the user to answer (though I am not
sure it can prevent Excel from
closing, but I heard about it a few
times)
Sending WM_CLOSE message to the main
Excel window
Executing the function that works
with Excel in a separate AppDomain.
Some people believe Excel instance
will be shut, when AppDomain is
unloaded.
Killing all excel instances which were instantiated after our excel-interoping code started.
BUT! Sometimes all these options just don't help or can't be appropriate!
For example, yesterday I found out that in one of my functions (which works with excel) Excel keeps running after the function ends. I tried everything! I thoroughly checked the whole function 10 times and added Marshal.FinalReleaseComObject() for everything! I also had GC.Collect() and GC.WaitForPendingFinalizers(). I checked for hidden message boxes. I tried to send WM_CLOSE message to the main Excel window. I executed my function in a separate AppDomain and unloaded that domain. Nothing helped! The option with closing all excel instances is inappropriate, because if the user starts another Excel instance manually, during execution of my function which works also with Excel, then that instance will also be closed by my function. I bet the user will not be happy! So, honestly, this is a lame option (no offence guys). So I spent a couple of hours before I found a good (in my humble opinion) solution: Kill excel process by hWnd of its main window (it's the first solution).
Here is the simple code:
[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 (Win32Exception)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
return true;
}
/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running).
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if (processID == 0)
throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
Process.GetProcessById((int)processID).Kill();
}
As you can see I provided two methods, according to Try-Parse pattern (I think it is appropriate here): one method doesn't throw the exception if the Process could not be killed (for example the process doesn't exist anymore), and another method throws the exception if the Process was not killed. The only weak place in this code is security permissions. Theoretically, the user may not have permissions to kill the process, but in 99.99% of all cases, user has such permissions. I also tested it with a guest account - it works perfectly.
So, your code, working with Excel, can look like this:
int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);
Voila! Excel is terminated! :)
Ok, let's go back to the second solution, as I promised in the beginning of the post.
The second solution is to call GC.Collect() and GC.WaitForPendingFinalizers(). Yes, they actually work, but you need to be careful here!
Many people say (and I said) that calling GC.Collect() doesn't help. But the reason it wouldn't help is if there are still references to COM objects! One of the most popular reasons for GC.Collect() not being helpful is running the project in Debug-mode. In debug-mode objects that are not really referenced anymore will not be garbage collected until the end of the method.
So, if you tried GC.Collect() and GC.WaitForPendingFinalizers() and it didn't help, try to do the following:
1) Try to run your project in Release mode and check if Excel closed correctly
2) Wrap the method of working with Excel in a separate method.
So, instead of something like this:
void GenerateWorkbook(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
you write:
void GenerateWorkbook(...)
{
try
{
GenerateWorkbookInternal(...);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void GenerateWorkbookInternal(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
}
}
Now, Excel will close =)
UPDATE: Added C# code, and link to Windows Jobs
I spent sometime trying to figure out this problem, and at the time XtremeVBTalk was the most active and responsive. Here is a link to my original post, Closing an Excel Interop process cleanly, even if your application crashes. Below is a summary of the post, and the code copied to this post.
Closing the Interop process with Application.Quit() and Process.Kill() works for the most part, but fails if the applications crashes catastrophically. I.e. if the app crashes, the Excel process will still be running loose.
The solution is to let the OS handle the cleanup of your processes through Windows Job Objects using Win32 calls. When your main application dies, the associated processes (i.e. Excel) will get terminated as well.
I found this to be a clean solution because the OS is doing real work of cleaning up. All you have to do is register the Excel process.
Windows Job Code
Wraps the Win32 API Calls to register Interop processes.
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public Int16 LimitFlags;
public UInt32 MinimumWorkingSetSize;
public UInt32 MaximumWorkingSetSize;
public Int16 ActiveProcessLimit;
public Int64 Affinity;
public Int16 PriorityClass;
public Int16 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UInt32 ProcessMemoryLimit;
public UInt32 JobMemoryLimit;
public UInt32 PeakProcessMemoryUsed;
public UInt32 PeakJobMemoryUsed;
}
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(object a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
private IntPtr m_handle;
private bool m_disposed = false;
public Job()
{
m_handle = CreateJobObject(null, null);
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private void Dispose(bool disposing)
{
if (m_disposed)
return;
if (disposing) {}
Close();
m_disposed = true;
}
public void Close()
{
Win32.CloseHandle(m_handle);
m_handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr handle)
{
return AssignProcessToJobObject(m_handle, handle);
}
}
Note about Constructor code
In the constructor, the info.LimitFlags = 0x2000; is called. 0x2000 is the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE enum value, and this value is defined by MSDN as:
Causes all processes associated with the job to terminate when the
last handle to the job is closed.
Extra Win32 API Call to get the Process ID (PID)
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Using the code
Excel.Application app = new Excel.ApplicationClass();
Job job = new Job();
uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
job.AddProcess(Process.GetProcessById((int)pid).Handle);
This worked for a project I was working on:
excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;
We learned that it was important to set every reference to an Excel COM object to null when you were done with it. This included Cells, Sheets, and everything.
First - you never have to call Marshal.ReleaseComObject(...) or Marshal.FinalReleaseComObject(...) when doing Excel interop. It is a confusing anti-pattern, but any information about this, including from Microsoft, that indicates you have to manually release COM references from .NET is incorrect. The fact is that the .NET runtime and garbage collector correctly keep track of and clean up COM references. For your code, this means you can remove the whole `while (...) loop at the top.
Second, if you want to ensure that the COM references to an out-of-process COM object are cleaned up when your process ends (so that the Excel process will close), you need to ensure that the garbage collector runs. You do this correctly with calls to GC.Collect() and GC.WaitForPendingFinalizers(). Calling this twice is safe, and ensures that cycles are definitely cleaned up too (though I'm not sure it's needed, and would appreciate an example that shows this).
Third, when running under the debugger, local references will be artificially kept alive until the end of the method (so that local variable inspection works). So GC.Collect() calls are not effective for cleaning object like rng.Cells from the same method. You should split the code doing the COM interop from the GC cleanup into separate methods. (This was a key discovery for me, from one part of the answer posted here by #nightcoder.)
The general pattern would thus be:
Sub WrapperThatCleansUp()
' NOTE: Don't call Excel objects in here...
' Debugger would keep alive until end, preventing GC cleanup
' Call a separate function that talks to Excel
DoTheWork()
' Now let the GC clean up (twice, to clean up cycles too)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
Sub DoTheWork()
Dim app As New Microsoft.Office.Interop.Excel.Application
Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
app.Visible = True
For i As Integer = 1 To 10
worksheet.Cells.Range("A" & i).Value = "Hello"
Next
book.Save()
book.Close()
app.Quit()
' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub
There is a lot of false information and confusion about this issue, including many posts on MSDN and on Stack Overflow (and especially this question!).
What finally convinced me to have a closer look and figure out the right advice was blog post Marshal.ReleaseComObject Considered Dangerous together with finding the issue with references kept alive under the debugger that was confusing my earlier testing.
Anything that is in the Excel namespace needs to be released. Period
You can't be doing:
Worksheet ws = excel.WorkBooks[1].WorkSheets[1];
You have to be doing
Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];
followed by the releasing of the objects.
I found a useful generic template that can help implement the correct disposal pattern for COM objects, that need Marshal.ReleaseComObject called when they go out of scope:
Usage:
using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
// do something with your workbook....
}
}
finally
{
excelApplicationWrapper.ComObject.Quit();
}
}
Template:
public class AutoReleaseComObject<T> : IDisposable
{
private T m_comObject;
private bool m_armed = true;
private bool m_disposed = false;
public AutoReleaseComObject(T comObject)
{
Debug.Assert(comObject != null);
m_comObject = comObject;
}
#if DEBUG
~AutoReleaseComObject()
{
// We should have been disposed using Dispose().
Debug.WriteLine("Finalize being called, should have been disposed");
if (this.ComObject != null)
{
Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
}
//Debug.Assert(false);
}
#endif
public T ComObject
{
get
{
Debug.Assert(!m_disposed);
return m_comObject;
}
}
private string ComObjectName
{
get
{
if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
{
return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
}
return null;
}
}
public void Disarm()
{
Debug.Assert(!m_disposed);
m_armed = false;
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
#endregion
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
if (m_armed)
{
int refcnt = 0;
do
{
refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
} while (refcnt > 0);
m_comObject = default(T);
}
m_disposed = true;
}
}
}
Reference:
http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
I cant believe this problem has haunted the world for 5 years.... If you have created an application, you need to shut it down first before removing the link.
objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
when closing
objBook.Close(true, Type.Missing, Type.Missing);
objExcel.Application.Quit();
objExcel.Quit();
When you new an excel application, it opens a excel program in the background. You need to command that excel program to quit before you release the link because that excel program is not part of your direct control. Therefore, it will stay open if the link is released!
Good programming everyone~~
Common developers, none of your solutions worked for me,
so I decide to implement a new trick.
First let specify "What is our goal?" => "Not to see excel object after our job in task manager"
Ok. Let no to challenge and start destroying it, but consider not to destroy other instance os Excel which are running in parallel.
So , get the list of current processors and fetch PID of EXCEL processes , then once your job is done, we have a new guest in processes list with a unique PID ,find and destroy just that one.
< keep in mind any new excel process during your excel job will be detected as new and destroyed >
< A better solution is to capture PID of new created excel object and just destroy that>
Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
if (p.ProcessName == "EXCEL")
excelPID.Add(p.Id);
.... // your job
prs = Process.GetProcesses();
foreach (Process p in prs)
if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
p.Kill();
This resolves my issue, hope yours too.
This sure seems like it has been over-complicated. From my experience, there are just three key things to get Excel to close properly:
1: make sure there are no remaining references to the excel application you created (you should only have one anyway; set it to null)
2: call GC.Collect()
3: Excel has to be closed, either by the user manually closing the program, or by you calling Quit on the Excel object. (Note that Quit will function just as if the user tried to close the program, and will present a confirmation dialog if there are unsaved changes, even if Excel is not visible. The user could press cancel, and then Excel will not have been closed.)
1 needs to happen before 2, but 3 can happen anytime.
One way to implement this is to wrap the interop Excel object with your own class, create the interop instance in the constructor, and implement IDisposable with Dispose looking something like
if (!mDisposed) {
mExcel = null;
GC.Collect();
mDisposed = true;
}
That will clean up excel from your program's side of things. Once Excel is closed (manually by the user or by you calling Quit) the process will go away. If the program has already been closed, then the process will disappear on the GC.Collect() call.
(I'm not sure how important it is, but you may want a GC.WaitForPendingFinalizers() call after the GC.Collect() call but it is not strictly necessary to get rid of the Excel process.)
This has worked for me without issue for years. Keep in mind though that while this works, you actually have to close gracefully for it to work. You will still get accumulating excel.exe processes if you interrupt your program before Excel is cleaned up (usually by hitting "stop" while your program is being debugged).
I've traditionally followed the advice found in VVS's answer. However, in an effort to keep this answer up-to-date with the latest options, I think all my future projects will use the "NetOffice" library.
NetOffice is a complete replacement for the Office PIAs and is completely version-agnostic. It's a collection of Managed COM wrappers that can handle the cleanup that often causes such headaches when working with Microsoft Office in .NET.
Some key features are:
Mostly version-independent (and version-dependant features are documented)
No dependencies
No PIA
No registration
No VSTO
I am in no way affiliated with the project; I just genuinely appreciate the stark reduction in headaches.
To add to reasons why Excel does not close, even when you create direct refrences to each object upon read, creation, is the 'For' loop.
For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
objWorkBook.Close 'or whatever
FinalReleaseComObject(objWorkBook)
objWorkBook = Nothing
Next
'The above does not work, and this is the workaround:
For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
objTempWorkBook.Saved = True
objTempWorkBook.Close(False, Type.Missing, Type.Missing)
FinalReleaseComObject(objTempWorkBook)
objTempWorkBook = Nothing
Next
The accepted answer here is correct, but also take note that not only "two dot" references need to be avoided, but also objects that are retrieved via the index. You also do not need to wait until you are finished with the program to clean up these objects, it's best to create functions that will clean them up as soon as you're finished with them, when possible. Here is a function I created that assigns some properties of a Style object called xlStyleHeader:
public Excel.Style xlStyleHeader = null;
private void CreateHeaderStyle()
{
Excel.Styles xlStyles = null;
Excel.Font xlFont = null;
Excel.Interior xlInterior = null;
Excel.Borders xlBorders = null;
Excel.Border xlBorderBottom = null;
try
{
xlStyles = xlWorkbook.Styles;
xlStyleHeader = xlStyles.Add("Header", Type.Missing);
// Text Format
xlStyleHeader.NumberFormat = "#";
// Bold
xlFont = xlStyleHeader.Font;
xlFont.Bold = true;
// Light Gray Cell Color
xlInterior = xlStyleHeader.Interior;
xlInterior.Color = 12632256;
// Medium Bottom border
xlBorders = xlStyleHeader.Borders;
xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
}
catch (Exception ex)
{
throw ex;
}
finally
{
Release(xlBorderBottom);
Release(xlBorders);
Release(xlInterior);
Release(xlFont);
Release(xlStyles);
}
}
private void Release(object obj)
{
// Errors are ignored per Microsoft's suggestion for this type of function:
// http://support.microsoft.com/default.aspx/kb/317109
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
}
catch { }
}
Notice that I had to set xlBorders[Excel.XlBordersIndex.xlEdgeBottom] to a variable in order to clean that up (Not because of the two dots, which refer to an enumeration which does not need to be released, but because the object I'm referring to is actually a Border object that does need to be released).
This sort of thing is not really necessary in standard applications, which do a great job of cleaning up after themselves, but in ASP.NET applications, if you miss even one of these, no matter how often you call the garbage collector, Excel will still be running on your server.
It requires a lot of attention to detail and many test executions while monitoring the Task Manager when writing this code, but doing so saves you the hassle of desperately searching through pages of code to find the one instance you missed. This is especially important when working in loops, where you need to release EACH INSTANCE of an object, even though it uses the same variable name each time it loops.
After trying
Release COM objects in reverse order
Add GC.Collect() and GC.WaitForPendingFinalizers() twice at the end
No more than two dots
Close workbook and quit application
Run in release mode
the final solution that works for me is to move one set of
GC.Collect();
GC.WaitForPendingFinalizers();
that we added to the end of the function to a wrapper, as follows:
private void FunctionWrapper(string sourcePath, string targetPath)
{
try
{
FunctionThatCallsExcel(sourcePath, targetPath);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
I followed this exactly... But I still ran into issues 1 out of 1000 times. Who knows why. Time to bring out the hammer...
Right after the Excel Application class is instantiated I get a hold of the Excel process that was just created.
excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();
Then once I've done all the above COM clean-up, I make sure that process isn't running. If it is still running, kill it!
if (!process.HasExited)
process.Kill();
¨°º¤ø„¸ Shoot Excel proc and chew bubble gum ¸„ø¤º°¨
public class MyExcelInteropClass
{
Excel.Application xlApp;
Excel.Workbook xlBook;
public void dothingswithExcel()
{
try { /* Do stuff manipulating cells sheets and workbooks ... */ }
catch {}
finally {KillExcelProcess(xlApp);}
}
static void KillExcelProcess(Excel.Application xlApp)
{
if (xlApp != null)
{
int excelProcessId = 0;
GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
Process p = Process.GetProcessById(excelProcessId);
p.Kill();
xlApp = null;
}
}
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}
You need to be aware that Excel is very sensitive to the culture you are running under as well.
You may find that you need to set the culture to EN-US before calling Excel functions.
This does not apply to all functions - but some of them.
CultureInfo en_US = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
string filePathLocal = _applicationObject.ActiveWorkbook.Path;
System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;
This applies even if you are using VSTO.
For details: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369
"Never use two dots with COM objects" is a great rule of thumb to avoid leakage of COM references, but Excel PIA can lead to leakage in more ways than apparent at first sight.
One of these ways is subscribing to any event exposed by any of the Excel object model's COM objects.
For example, subscribing to the Application class's WorkbookOpen event.
Some theory on COM events
COM classes expose a group of events through call-back interfaces. In order to subscribe to events, the client code can simply register an object implementing the call-back interface and the COM class will invoke its methods in response to specific events. Since the call-back interface is a COM interface, it is the duty of the implementing object to decrement the reference count of any COM object it receives (as a parameter) for any of the event handlers.
How Excel PIA expose COM Events
Excel PIA exposes COM events of Excel Application class as conventional .NET events. Whenever the client code subscribes to a .NET event (emphasis on 'a'), PIA creates an instance of a class implementing the call-back interface and registers it with Excel.
Hence, a number of call-back objects get registered with Excel in response to different subscription requests from the .NET code. One call-back object per event subscription.
A call-back interface for event handling means that, PIA has to subscribe to all interface events for every .NET event subscription request. It cannot pick and choose. On receiving an event call-back, the call-back object checks if the associated .NET event handler is interested in the current event or not and then either invokes the handler or silently ignores the call-back.
Effect on COM instance reference counts
All these call-back objects do not decrement the reference count of any of the COM objects they receive (as parameters) for any of the call-back methods (even for the ones that are silently ignored). They rely solely on the CLR garbage collector to free up the COM objects.
Since GC run is non-deterministic, this can lead to the holding off of Excel process for a longer duration than desired and create an impression of a 'memory leak'.
Solution
The only solution as of now is to avoid the PIA’s event provider for the COM class and write your own event provider which deterministically releases COM objects.
For the Application class, this can be done by implementing the AppEvents interface and then registering the implementation with Excel by using IConnectionPointContainer interface. The Application class (and for that matter all COM objects exposing events using callback mechanism) implements the IConnectionPointContainer interface.
A great article on releasing COM objects is 2.5 Releasing COM Objects (MSDN).
The method that I would advocate is to null your Excel.Interop references if they are non-local variables, and then call GC.Collect() and GC.WaitForPendingFinalizers() twice. Locally scoped Interop variables will be taken care of automatically.
This removes the need to keep a named reference for every COM object.
Here's an example taken from the article:
public class Test {
// These instance variables must be nulled or Excel will not quit
private Excel.Application xl;
private Excel.Workbook book;
public void DoSomething()
{
xl = new Excel.Application();
xl.Visible = true;
book = xl.Workbooks.Add(Type.Missing);
// These variables are locally scoped, so we need not worry about them.
// Notice I don't care about using two dots.
Excel.Range rng = book.Worksheets[1].UsedRange;
}
public void CleanUp()
{
book = null;
xl.Quit();
xl = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
These words are straight from the article:
In almost all situations, nulling the RCW reference and forcing a garbage collection will clean up properly. If you also call GC.WaitForPendingFinalizers, garbage collection will be as deterministic as you can make it. That is, you'll be pretty sure exactly when the object has been cleaned up—on the return from the second call to WaitForPendingFinalizers. As an alternative, you can use Marshal.ReleaseComObject. However, note that you are very unlikely to ever need to use this method.
As others have pointed out, you need to create an explicit reference for every Excel object you use, and call Marshal.ReleaseComObject on that reference, as described in this KB article. You also need to use try/finally to ensure ReleaseComObject is always called, even when an exception is thrown. I.e. instead of:
Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet
you need to do something like:
Worksheets sheets = null;
Worksheet sheet = null
try
{
sheets = excelApp.Worksheets;
sheet = sheets(1);
...
}
finally
{
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (sheet != null) Marshal.ReleaseComObject(sheet);
}
You also need to call Application.Quit before releasing the Application object if you want Excel to close.
As you can see, this quickly becomes extremely unwieldy as soon as you try to do anything even moderately complex. I have successfully developed .NET applications with a simple wrapper class that wraps a few simple manipulations of the Excel object model (open a workbook, write to a Range, save/close the workbook etc). The wrapper class implements IDisposable, carefully implements Marshal.ReleaseComObject on every object it uses, and does not pubicly expose any Excel objects to the rest of the app.
But this approach doesn't scale well for more complex requirements.
This is a big deficiency of .NET COM Interop. For more complex scenarios, I would seriously consider writing an ActiveX DLL in VB6 or other unmanaged language to which you can delegate all interaction with out-proc COM objects such as Office. You can then reference this ActiveX DLL from your .NET application, and things will be much easier as you will only need to release this one reference.
When all the stuff above didn't work, try giving Excel some time to close its sheets:
app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();
...
FinalReleaseComObject(app);
Make sure that you release all objects related to Excel!
I spent a few hours by trying several ways. All are great ideas but I finally found my mistake: If you don't release all objects, none of the ways above can help you like in my case. Make sure you release all objects including range one!
Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);
The options are together here.
The two dots rule did not work for me. In my case I created a method to clean my resources as follows:
private static void Clean()
{
workBook.Close();
Marshall.ReleaseComObject(workBook);
excel.Quit();
CG.Collect();
CG.WaitForPendingFinalizers();
}
My solution
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
private void GenerateExcel()
{
var excel = new Microsoft.Office.Interop.Excel.Application();
int id;
// Find the Excel Process Id (ath the end, you kill him
GetWindowThreadProcessId(excel.Hwnd, out id);
Process excelProcess = Process.GetProcessById(id);
try
{
// Your code
}
finally
{
excel.Quit();
// Kill him !
excelProcess.Kill();
}
You should be very careful using Word/Excel interop applications. After trying all the solutions we still had a lot of "WinWord" process left open on server (with more than 2000 users).
After working on the problem for hours, I realized that if I open more than a couple of documents using Word.ApplicationClass.Document.Open() on different threads simultaneously, IIS worker process (w3wp.exe) would crash leaving all WinWord processes open!
So I guess there is no absolute solution to this problem, but switching to other methods such as Office Open XML development.
The accepted answer did not work for me. The following code in the destructor did the job.
if (xlApp != null)
{
xlApp.Workbooks.Close();
xlApp.Quit();
}
System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
I am currently working on Office automation and have stumbled across a solution for this that works every time for me. It is simple and does not involve killing any processes.
It seems that by merely looping through the current active processes, and in any way 'accessing' an open Excel process, any stray hanging instance of Excel will be removed. The below code simply checks for processes where the name is 'Excel', then writes the MainWindowTitle property of the process to a string. This 'interaction' with the process seems to make Windows catch up and abort the frozen instance of Excel.
I run the below method just before the add-in which I am developing quits, as it fires it unloading event. It removes any hanging instances of Excel every time. In all honesty I am not entirely sure why this works, but it works well for me and could be placed at the end of any Excel application without having to worry about double dots, Marshal.ReleaseComObject, nor killing processes. I would be very interested in any suggestions as to why this is effective.
public static void SweepExcelProcesses()
{
if (Process.GetProcessesByName("EXCEL").Length != 0)
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName.ToString() == "excel")
{
string title = process.MainWindowTitle;
}
}
}
}
I think that some of that is just the way that the framework handles Office applications, but I could be wrong. On some days, some applications clean up the processes immediately, and other days it seems to wait until the application closes. In general, I quit paying attention to the details and just make sure that there aren't any extra processes floating around at the end of the day.
Also, and maybe I'm over simplifying things, but I think you can just...
objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();
Like I said earlier, I don't tend to pay attention to the details of when the Excel process appears or disappears, but that usually works for me. I also don't like to keep Excel processes around for anything other than the minimal amount of time, but I'm probably just being paranoid on that.
As some have probably already written, it's not just important how you close the Excel (object); it's also important how you open it and also by the type of the project.
In a WPF application, basically the same code is working without or with very few problems.
I have a project in which the same Excel file is being processed several times for different parameter value - e.g. parsing it based on values inside a generic list.
I put all Excel-related functions into the base class, and parser into a subclass (different parsers use common Excel functions). I didn't want that Excel is opened and closed again for each item in a generic list, so I've opened it only once in the base class and close it in the subclass. I had problems when moving the code into a desktop application. I've tried many of the above mentioned solutions. GC.Collect() was already implemented before, twice as suggested.
Then I've decided that I will move the code for opening Excel to a subclass. Instead of opening only once, now I create a new object (base class) and open Excel for every item and close it at the end. There is some performance penalty, but based on several tests Excel processes are closing without problems (in debug mode), so also temporary files are removed. I will continue with testing and write some more if I will get some updates.
The bottom line is: You must also check the initialize code, especially if you have many classes, etc.
Related
Release excel objects [duplicate]
I'm using the Excel interop in C# (ApplicationClass) and have placed the following code in my finally clause: while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { } excelSheet = null; GC.Collect(); GC.WaitForPendingFinalizers(); Although this kind of works, the Excel.exe process is still in the background even after I close Excel. It is only released once my application is manually closed. What am I doing wrong, or is there an alternative to ensure interop objects are properly disposed of?
Excel does not quit because your application is still holding references to COM objects. I guess you're invoking at least one member of a COM object without assigning it to a variable. For me it was the excelApp.Worksheets object which I directly used without assigning it to a variable: Worksheet sheet = excelApp.Worksheets.Open(...); ... Marshal.ReleaseComObject(sheet); I didn't know that internally C# created a wrapper for the Worksheets COM object which didn't get released by my code (because I wasn't aware of it) and was the cause why Excel was not unloaded. I found the solution to my problem on this page, which also has a nice rule for the usage of COM objects in C#: Never use two dots with COM objects. So with this knowledge the right way of doing the above is: Worksheets sheets = excelApp.Worksheets; // <-- The important part Worksheet sheet = sheets.Open(...); ... Marshal.ReleaseComObject(sheets); Marshal.ReleaseComObject(sheet); POST MORTEM UPDATE: I want every reader to read this answer by Hans Passant very carefully as it explains the trap I and lots of other developers stumbled into. When I wrote this answer years ago I didn't know about the effect the debugger has to the garbage collector and drew the wrong conclusions. I keep my answer unaltered for the sake of history but please read this link and don't go the way of "the two dots": Understanding garbage collection in .NET and Clean up Excel Interop Objects with IDisposable
You can actually release your Excel Application object cleanly, but you do have to take care. The advice to maintain a named reference for absolutely every COM object you access and then explicitly release it via Marshal.FinalReleaseComObject() is correct in theory, but, unfortunately, very difficult to manage in practice. If one ever slips anywhere and uses "two dots", or iterates cells via a for each loop, or any other similar kind of command, then you'll have unreferenced COM objects and risk a hang. In this case, there would be no way to find the cause in the code; you would have to review all your code by eye and hopefully find the cause, a task that could be nearly impossible for a large project. The good news is that you do not actually have to maintain a named variable reference to every COM object you use. Instead, call GC.Collect() and then GC.WaitForPendingFinalizers() to release all the (usually minor) objects to which you do not hold a reference, and then explicitly release the objects to which you do hold a named variable reference. You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object. For example, assuming that you had a Range object variable named xlRng, a Worksheet variable named xlSheet, a Workbook variable named xlBook and an Excel Application variable named xlApp, then your cleanup code could look something like the following: // Cleanup GC.Collect(); GC.WaitForPendingFinalizers(); Marshal.FinalReleaseComObject(xlRng); Marshal.FinalReleaseComObject(xlSheet); xlBook.Close(Type.Missing, Type.Missing, Type.Missing); Marshal.FinalReleaseComObject(xlBook); xlApp.Quit(); Marshal.FinalReleaseComObject(xlApp); In most code examples you'll see for cleaning up COM objects from .NET, the GC.Collect() and GC.WaitForPendingFinalizers() calls are made TWICE as in: GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); This should not be required, however, unless you are using Visual Studio Tools for Office (VSTO), which uses finalizers that cause an entire graph of objects to be promoted in the finalization queue. Such objects would not be released until the next garbage collection. However, if you are not using VSTO, you should be able to call GC.Collect() and GC.WaitForPendingFinalizers() just once. I know that explicitly calling GC.Collect() is a no-no (and certainly doing it twice sounds very painful), but there is no way around it, to be honest. Through normal operations you will generate hidden objects to which you hold no reference that you, therefore, cannot release through any other means other than calling GC.Collect(). This is a complex topic, but this really is all there is to it. Once you establish this template for your cleanup procedure you can code normally, without the need for wrappers, etc. :-) I have a tutorial on this here: Automating Office Programs with VB.Net / COM Interop It's written for VB.NET, but don't be put off by that, the principles are exactly the same as when using C#.
Preface: my answer contains two solutions, so be careful when reading and don't miss anything. There are different ways and advice of how to make Excel instance unload, such as: Releasing EVERY com object explicitly with Marshal.FinalReleaseComObject() (not forgetting about implicitly created com-objects). To release every created com object, you may use the rule of 2 dots mentioned here: How do I properly clean up Excel interop objects? Calling GC.Collect() and GC.WaitForPendingFinalizers() to make CLR release unused com-objects * (Actually, it works, see my second solution for details) Checking if com-server-application maybe shows a message box waiting for the user to answer (though I am not sure it can prevent Excel from closing, but I heard about it a few times) Sending WM_CLOSE message to the main Excel window Executing the function that works with Excel in a separate AppDomain. Some people believe Excel instance will be shut, when AppDomain is unloaded. Killing all excel instances which were instantiated after our excel-interoping code started. BUT! Sometimes all these options just don't help or can't be appropriate! For example, yesterday I found out that in one of my functions (which works with excel) Excel keeps running after the function ends. I tried everything! I thoroughly checked the whole function 10 times and added Marshal.FinalReleaseComObject() for everything! I also had GC.Collect() and GC.WaitForPendingFinalizers(). I checked for hidden message boxes. I tried to send WM_CLOSE message to the main Excel window. I executed my function in a separate AppDomain and unloaded that domain. Nothing helped! The option with closing all excel instances is inappropriate, because if the user starts another Excel instance manually, during execution of my function which works also with Excel, then that instance will also be closed by my function. I bet the user will not be happy! So, honestly, this is a lame option (no offence guys). So I spent a couple of hours before I found a good (in my humble opinion) solution: Kill excel process by hWnd of its main window (it's the first solution). Here is the simple code: [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 (Win32Exception) { return false; } catch (NotSupportedException) { return false; } catch (InvalidOperationException) { return false; } return true; } /// <summary> Finds and kills process by hWnd to the main window of the process.</summary> /// <param name="hWnd">Handle to the main window of the process.</param> /// <exception cref="ArgumentException"> /// Thrown when process is not found by the hWnd parameter (the process is not running). /// The identifier of the process might be expired. /// </exception> /// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception> /// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception> /// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception> public static void KillProcessByMainWindowHwnd(int hWnd) { uint processID; GetWindowThreadProcessId((IntPtr)hWnd, out processID); if (processID == 0) throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd"); Process.GetProcessById((int)processID).Kill(); } As you can see I provided two methods, according to Try-Parse pattern (I think it is appropriate here): one method doesn't throw the exception if the Process could not be killed (for example the process doesn't exist anymore), and another method throws the exception if the Process was not killed. The only weak place in this code is security permissions. Theoretically, the user may not have permissions to kill the process, but in 99.99% of all cases, user has such permissions. I also tested it with a guest account - it works perfectly. So, your code, working with Excel, can look like this: int hWnd = xl.Application.Hwnd; // ... // here we try to close Excel as usual, with xl.Quit(), // Marshal.FinalReleaseComObject(xl) and so on // ... TryKillProcessByMainWindowHwnd(hWnd); Voila! Excel is terminated! :) Ok, let's go back to the second solution, as I promised in the beginning of the post. The second solution is to call GC.Collect() and GC.WaitForPendingFinalizers(). Yes, they actually work, but you need to be careful here! Many people say (and I said) that calling GC.Collect() doesn't help. But the reason it wouldn't help is if there are still references to COM objects! One of the most popular reasons for GC.Collect() not being helpful is running the project in Debug-mode. In debug-mode objects that are not really referenced anymore will not be garbage collected until the end of the method. So, if you tried GC.Collect() and GC.WaitForPendingFinalizers() and it didn't help, try to do the following: 1) Try to run your project in Release mode and check if Excel closed correctly 2) Wrap the method of working with Excel in a separate method. So, instead of something like this: void GenerateWorkbook(...) { ApplicationClass xl; Workbook xlWB; try { xl = ... xlWB = xl.Workbooks.Add(...); ... } finally { ... Marshal.ReleaseComObject(xlWB) ... GC.Collect(); GC.WaitForPendingFinalizers(); } } you write: void GenerateWorkbook(...) { try { GenerateWorkbookInternal(...); } finally { GC.Collect(); GC.WaitForPendingFinalizers(); } } private void GenerateWorkbookInternal(...) { ApplicationClass xl; Workbook xlWB; try { xl = ... xlWB = xl.Workbooks.Add(...); ... } finally { ... Marshal.ReleaseComObject(xlWB) ... } } Now, Excel will close =)
UPDATE: Added C# code, and link to Windows Jobs I spent sometime trying to figure out this problem, and at the time XtremeVBTalk was the most active and responsive. Here is a link to my original post, Closing an Excel Interop process cleanly, even if your application crashes. Below is a summary of the post, and the code copied to this post. Closing the Interop process with Application.Quit() and Process.Kill() works for the most part, but fails if the applications crashes catastrophically. I.e. if the app crashes, the Excel process will still be running loose. The solution is to let the OS handle the cleanup of your processes through Windows Job Objects using Win32 calls. When your main application dies, the associated processes (i.e. Excel) will get terminated as well. I found this to be a clean solution because the OS is doing real work of cleaning up. All you have to do is register the Excel process. Windows Job Code Wraps the Win32 API Calls to register Interop processes. public enum JobObjectInfoType { AssociateCompletionPortInformation = 7, BasicLimitInformation = 2, BasicUIRestrictions = 4, EndOfJobTimeInformation = 6, ExtendedLimitInformation = 9, SecurityLimitInformation = 5, GroupInformation = 11 } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } [StructLayout(LayoutKind.Sequential)] struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public Int64 PerProcessUserTimeLimit; public Int64 PerJobUserTimeLimit; public Int16 LimitFlags; public UInt32 MinimumWorkingSetSize; public UInt32 MaximumWorkingSetSize; public Int16 ActiveProcessLimit; public Int64 Affinity; public Int16 PriorityClass; public Int16 SchedulingClass; } [StructLayout(LayoutKind.Sequential)] struct IO_COUNTERS { public UInt64 ReadOperationCount; public UInt64 WriteOperationCount; public UInt64 OtherOperationCount; public UInt64 ReadTransferCount; public UInt64 WriteTransferCount; public UInt64 OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UInt32 ProcessMemoryLimit; public UInt32 JobMemoryLimit; public UInt32 PeakProcessMemoryUsed; public UInt32 PeakJobMemoryUsed; } public class Job : IDisposable { [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr CreateJobObject(object a, string lpName); [DllImport("kernel32.dll")] static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); private IntPtr m_handle; private bool m_disposed = false; public Job() { m_handle = CreateJobObject(null, null); JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); info.LimitFlags = 0x2000; JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); extendedInfo.BasicLimitInformation = info; int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error())); } #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion private void Dispose(bool disposing) { if (m_disposed) return; if (disposing) {} Close(); m_disposed = true; } public void Close() { Win32.CloseHandle(m_handle); m_handle = IntPtr.Zero; } public bool AddProcess(IntPtr handle) { return AssignProcessToJobObject(m_handle, handle); } } Note about Constructor code In the constructor, the info.LimitFlags = 0x2000; is called. 0x2000 is the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE enum value, and this value is defined by MSDN as: Causes all processes associated with the job to terminate when the last handle to the job is closed. Extra Win32 API Call to get the Process ID (PID) [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); Using the code Excel.Application app = new Excel.ApplicationClass(); Job job = new Job(); uint pid = 0; Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid); job.AddProcess(Process.GetProcessById((int)pid).Handle);
This worked for a project I was working on: excelApp.Quit(); Marshal.ReleaseComObject (excelWB); Marshal.ReleaseComObject (excelApp); excelApp = null; We learned that it was important to set every reference to an Excel COM object to null when you were done with it. This included Cells, Sheets, and everything.
First - you never have to call Marshal.ReleaseComObject(...) or Marshal.FinalReleaseComObject(...) when doing Excel interop. It is a confusing anti-pattern, but any information about this, including from Microsoft, that indicates you have to manually release COM references from .NET is incorrect. The fact is that the .NET runtime and garbage collector correctly keep track of and clean up COM references. For your code, this means you can remove the whole `while (...) loop at the top. Second, if you want to ensure that the COM references to an out-of-process COM object are cleaned up when your process ends (so that the Excel process will close), you need to ensure that the garbage collector runs. You do this correctly with calls to GC.Collect() and GC.WaitForPendingFinalizers(). Calling this twice is safe, and ensures that cycles are definitely cleaned up too (though I'm not sure it's needed, and would appreciate an example that shows this). Third, when running under the debugger, local references will be artificially kept alive until the end of the method (so that local variable inspection works). So GC.Collect() calls are not effective for cleaning object like rng.Cells from the same method. You should split the code doing the COM interop from the GC cleanup into separate methods. (This was a key discovery for me, from one part of the answer posted here by #nightcoder.) The general pattern would thus be: Sub WrapperThatCleansUp() ' NOTE: Don't call Excel objects in here... ' Debugger would keep alive until end, preventing GC cleanup ' Call a separate function that talks to Excel DoTheWork() ' Now let the GC clean up (twice, to clean up cycles too) GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() End Sub Sub DoTheWork() Dim app As New Microsoft.Office.Interop.Excel.Application Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add() Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1") app.Visible = True For i As Integer = 1 To 10 worksheet.Cells.Range("A" & i).Value = "Hello" Next book.Save() book.Close() app.Quit() ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed End Sub There is a lot of false information and confusion about this issue, including many posts on MSDN and on Stack Overflow (and especially this question!). What finally convinced me to have a closer look and figure out the right advice was blog post Marshal.ReleaseComObject Considered Dangerous together with finding the issue with references kept alive under the debugger that was confusing my earlier testing.
Anything that is in the Excel namespace needs to be released. Period You can't be doing: Worksheet ws = excel.WorkBooks[1].WorkSheets[1]; You have to be doing Workbooks books = excel.WorkBooks; Workbook book = books[1]; Sheets sheets = book.WorkSheets; Worksheet ws = sheets[1]; followed by the releasing of the objects.
I found a useful generic template that can help implement the correct disposal pattern for COM objects, that need Marshal.ReleaseComObject called when they go out of scope: Usage: using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application())) { try { using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing))) { // do something with your workbook.... } } finally { excelApplicationWrapper.ComObject.Quit(); } } Template: public class AutoReleaseComObject<T> : IDisposable { private T m_comObject; private bool m_armed = true; private bool m_disposed = false; public AutoReleaseComObject(T comObject) { Debug.Assert(comObject != null); m_comObject = comObject; } #if DEBUG ~AutoReleaseComObject() { // We should have been disposed using Dispose(). Debug.WriteLine("Finalize being called, should have been disposed"); if (this.ComObject != null) { Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName)); } //Debug.Assert(false); } #endif public T ComObject { get { Debug.Assert(!m_disposed); return m_comObject; } } private string ComObjectName { get { if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook) { return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name; } return null; } } public void Disarm() { Debug.Assert(!m_disposed); m_armed = false; } #region IDisposable Members public void Dispose() { Dispose(true); #if DEBUG GC.SuppressFinalize(this); #endif } #endregion protected virtual void Dispose(bool disposing) { if (!m_disposed) { if (m_armed) { int refcnt = 0; do { refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject); } while (refcnt > 0); m_comObject = default(T); } m_disposed = true; } } } Reference: http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
I cant believe this problem has haunted the world for 5 years.... If you have created an application, you need to shut it down first before removing the link. objExcel = new Excel.Application(); objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); when closing objBook.Close(true, Type.Missing, Type.Missing); objExcel.Application.Quit(); objExcel.Quit(); When you new an excel application, it opens a excel program in the background. You need to command that excel program to quit before you release the link because that excel program is not part of your direct control. Therefore, it will stay open if the link is released! Good programming everyone~~
Common developers, none of your solutions worked for me, so I decide to implement a new trick. First let specify "What is our goal?" => "Not to see excel object after our job in task manager" Ok. Let no to challenge and start destroying it, but consider not to destroy other instance os Excel which are running in parallel. So , get the list of current processors and fetch PID of EXCEL processes , then once your job is done, we have a new guest in processes list with a unique PID ,find and destroy just that one. < keep in mind any new excel process during your excel job will be detected as new and destroyed > < A better solution is to capture PID of new created excel object and just destroy that> Process[] prs = Process.GetProcesses(); List<int> excelPID = new List<int>(); foreach (Process p in prs) if (p.ProcessName == "EXCEL") excelPID.Add(p.Id); .... // your job prs = Process.GetProcesses(); foreach (Process p in prs) if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id)) p.Kill(); This resolves my issue, hope yours too.
This sure seems like it has been over-complicated. From my experience, there are just three key things to get Excel to close properly: 1: make sure there are no remaining references to the excel application you created (you should only have one anyway; set it to null) 2: call GC.Collect() 3: Excel has to be closed, either by the user manually closing the program, or by you calling Quit on the Excel object. (Note that Quit will function just as if the user tried to close the program, and will present a confirmation dialog if there are unsaved changes, even if Excel is not visible. The user could press cancel, and then Excel will not have been closed.) 1 needs to happen before 2, but 3 can happen anytime. One way to implement this is to wrap the interop Excel object with your own class, create the interop instance in the constructor, and implement IDisposable with Dispose looking something like if (!mDisposed) { mExcel = null; GC.Collect(); mDisposed = true; } That will clean up excel from your program's side of things. Once Excel is closed (manually by the user or by you calling Quit) the process will go away. If the program has already been closed, then the process will disappear on the GC.Collect() call. (I'm not sure how important it is, but you may want a GC.WaitForPendingFinalizers() call after the GC.Collect() call but it is not strictly necessary to get rid of the Excel process.) This has worked for me without issue for years. Keep in mind though that while this works, you actually have to close gracefully for it to work. You will still get accumulating excel.exe processes if you interrupt your program before Excel is cleaned up (usually by hitting "stop" while your program is being debugged).
I've traditionally followed the advice found in VVS's answer. However, in an effort to keep this answer up-to-date with the latest options, I think all my future projects will use the "NetOffice" library. NetOffice is a complete replacement for the Office PIAs and is completely version-agnostic. It's a collection of Managed COM wrappers that can handle the cleanup that often causes such headaches when working with Microsoft Office in .NET. Some key features are: Mostly version-independent (and version-dependant features are documented) No dependencies No PIA No registration No VSTO I am in no way affiliated with the project; I just genuinely appreciate the stark reduction in headaches.
To add to reasons why Excel does not close, even when you create direct refrences to each object upon read, creation, is the 'For' loop. For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot objWorkBook.Close 'or whatever FinalReleaseComObject(objWorkBook) objWorkBook = Nothing Next 'The above does not work, and this is the workaround: For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter) objTempWorkBook.Saved = True objTempWorkBook.Close(False, Type.Missing, Type.Missing) FinalReleaseComObject(objTempWorkBook) objTempWorkBook = Nothing Next
The accepted answer here is correct, but also take note that not only "two dot" references need to be avoided, but also objects that are retrieved via the index. You also do not need to wait until you are finished with the program to clean up these objects, it's best to create functions that will clean them up as soon as you're finished with them, when possible. Here is a function I created that assigns some properties of a Style object called xlStyleHeader: public Excel.Style xlStyleHeader = null; private void CreateHeaderStyle() { Excel.Styles xlStyles = null; Excel.Font xlFont = null; Excel.Interior xlInterior = null; Excel.Borders xlBorders = null; Excel.Border xlBorderBottom = null; try { xlStyles = xlWorkbook.Styles; xlStyleHeader = xlStyles.Add("Header", Type.Missing); // Text Format xlStyleHeader.NumberFormat = "#"; // Bold xlFont = xlStyleHeader.Font; xlFont.Bold = true; // Light Gray Cell Color xlInterior = xlStyleHeader.Interior; xlInterior.Color = 12632256; // Medium Bottom border xlBorders = xlStyleHeader.Borders; xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom]; xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium; } catch (Exception ex) { throw ex; } finally { Release(xlBorderBottom); Release(xlBorders); Release(xlInterior); Release(xlFont); Release(xlStyles); } } private void Release(object obj) { // Errors are ignored per Microsoft's suggestion for this type of function: // http://support.microsoft.com/default.aspx/kb/317109 try { System.Runtime.InteropServices.Marshal.ReleaseComObject(obj); } catch { } } Notice that I had to set xlBorders[Excel.XlBordersIndex.xlEdgeBottom] to a variable in order to clean that up (Not because of the two dots, which refer to an enumeration which does not need to be released, but because the object I'm referring to is actually a Border object that does need to be released). This sort of thing is not really necessary in standard applications, which do a great job of cleaning up after themselves, but in ASP.NET applications, if you miss even one of these, no matter how often you call the garbage collector, Excel will still be running on your server. It requires a lot of attention to detail and many test executions while monitoring the Task Manager when writing this code, but doing so saves you the hassle of desperately searching through pages of code to find the one instance you missed. This is especially important when working in loops, where you need to release EACH INSTANCE of an object, even though it uses the same variable name each time it loops.
After trying Release COM objects in reverse order Add GC.Collect() and GC.WaitForPendingFinalizers() twice at the end No more than two dots Close workbook and quit application Run in release mode the final solution that works for me is to move one set of GC.Collect(); GC.WaitForPendingFinalizers(); that we added to the end of the function to a wrapper, as follows: private void FunctionWrapper(string sourcePath, string targetPath) { try { FunctionThatCallsExcel(sourcePath, targetPath); } finally { GC.Collect(); GC.WaitForPendingFinalizers(); } }
I followed this exactly... But I still ran into issues 1 out of 1000 times. Who knows why. Time to bring out the hammer... Right after the Excel Application class is instantiated I get a hold of the Excel process that was just created. excel = new Microsoft.Office.Interop.Excel.Application(); var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First(); Then once I've done all the above COM clean-up, I make sure that process isn't running. If it is still running, kill it! if (!process.HasExited) process.Kill();
¨°º¤ø„¸ Shoot Excel proc and chew bubble gum ¸„ø¤º°¨ public class MyExcelInteropClass { Excel.Application xlApp; Excel.Workbook xlBook; public void dothingswithExcel() { try { /* Do stuff manipulating cells sheets and workbooks ... */ } catch {} finally {KillExcelProcess(xlApp);} } static void KillExcelProcess(Excel.Application xlApp) { if (xlApp != null) { int excelProcessId = 0; GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId); Process p = Process.GetProcessById(excelProcessId); p.Kill(); xlApp = null; } } [DllImport("user32.dll")] static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId); }
You need to be aware that Excel is very sensitive to the culture you are running under as well. You may find that you need to set the culture to EN-US before calling Excel functions. This does not apply to all functions - but some of them. CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); System.Threading.Thread.CurrentThread.CurrentCulture = en_US; string filePathLocal = _applicationObject.ActiveWorkbook.Path; System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture; This applies even if you are using VSTO. For details: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369
"Never use two dots with COM objects" is a great rule of thumb to avoid leakage of COM references, but Excel PIA can lead to leakage in more ways than apparent at first sight. One of these ways is subscribing to any event exposed by any of the Excel object model's COM objects. For example, subscribing to the Application class's WorkbookOpen event. Some theory on COM events COM classes expose a group of events through call-back interfaces. In order to subscribe to events, the client code can simply register an object implementing the call-back interface and the COM class will invoke its methods in response to specific events. Since the call-back interface is a COM interface, it is the duty of the implementing object to decrement the reference count of any COM object it receives (as a parameter) for any of the event handlers. How Excel PIA expose COM Events Excel PIA exposes COM events of Excel Application class as conventional .NET events. Whenever the client code subscribes to a .NET event (emphasis on 'a'), PIA creates an instance of a class implementing the call-back interface and registers it with Excel. Hence, a number of call-back objects get registered with Excel in response to different subscription requests from the .NET code. One call-back object per event subscription. A call-back interface for event handling means that, PIA has to subscribe to all interface events for every .NET event subscription request. It cannot pick and choose. On receiving an event call-back, the call-back object checks if the associated .NET event handler is interested in the current event or not and then either invokes the handler or silently ignores the call-back. Effect on COM instance reference counts All these call-back objects do not decrement the reference count of any of the COM objects they receive (as parameters) for any of the call-back methods (even for the ones that are silently ignored). They rely solely on the CLR garbage collector to free up the COM objects. Since GC run is non-deterministic, this can lead to the holding off of Excel process for a longer duration than desired and create an impression of a 'memory leak'. Solution The only solution as of now is to avoid the PIA’s event provider for the COM class and write your own event provider which deterministically releases COM objects. For the Application class, this can be done by implementing the AppEvents interface and then registering the implementation with Excel by using IConnectionPointContainer interface. The Application class (and for that matter all COM objects exposing events using callback mechanism) implements the IConnectionPointContainer interface.
A great article on releasing COM objects is 2.5 Releasing COM Objects (MSDN). The method that I would advocate is to null your Excel.Interop references if they are non-local variables, and then call GC.Collect() and GC.WaitForPendingFinalizers() twice. Locally scoped Interop variables will be taken care of automatically. This removes the need to keep a named reference for every COM object. Here's an example taken from the article: public class Test { // These instance variables must be nulled or Excel will not quit private Excel.Application xl; private Excel.Workbook book; public void DoSomething() { xl = new Excel.Application(); xl.Visible = true; book = xl.Workbooks.Add(Type.Missing); // These variables are locally scoped, so we need not worry about them. // Notice I don't care about using two dots. Excel.Range rng = book.Worksheets[1].UsedRange; } public void CleanUp() { book = null; xl.Quit(); xl = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); } } These words are straight from the article: In almost all situations, nulling the RCW reference and forcing a garbage collection will clean up properly. If you also call GC.WaitForPendingFinalizers, garbage collection will be as deterministic as you can make it. That is, you'll be pretty sure exactly when the object has been cleaned up—on the return from the second call to WaitForPendingFinalizers. As an alternative, you can use Marshal.ReleaseComObject. However, note that you are very unlikely to ever need to use this method.
As others have pointed out, you need to create an explicit reference for every Excel object you use, and call Marshal.ReleaseComObject on that reference, as described in this KB article. You also need to use try/finally to ensure ReleaseComObject is always called, even when an exception is thrown. I.e. instead of: Worksheet sheet = excelApp.Worksheets(1) ... do something with sheet you need to do something like: Worksheets sheets = null; Worksheet sheet = null try { sheets = excelApp.Worksheets; sheet = sheets(1); ... } finally { if (sheets != null) Marshal.ReleaseComObject(sheets); if (sheet != null) Marshal.ReleaseComObject(sheet); } You also need to call Application.Quit before releasing the Application object if you want Excel to close. As you can see, this quickly becomes extremely unwieldy as soon as you try to do anything even moderately complex. I have successfully developed .NET applications with a simple wrapper class that wraps a few simple manipulations of the Excel object model (open a workbook, write to a Range, save/close the workbook etc). The wrapper class implements IDisposable, carefully implements Marshal.ReleaseComObject on every object it uses, and does not pubicly expose any Excel objects to the rest of the app. But this approach doesn't scale well for more complex requirements. This is a big deficiency of .NET COM Interop. For more complex scenarios, I would seriously consider writing an ActiveX DLL in VB6 or other unmanaged language to which you can delegate all interaction with out-proc COM objects such as Office. You can then reference this ActiveX DLL from your .NET application, and things will be much easier as you will only need to release this one reference.
When all the stuff above didn't work, try giving Excel some time to close its sheets: app.workbooks.Close(); Thread.Sleep(500); // adjust, for me it works at around 300+ app.Quit(); ... FinalReleaseComObject(app);
Make sure that you release all objects related to Excel! I spent a few hours by trying several ways. All are great ideas but I finally found my mistake: If you don't release all objects, none of the ways above can help you like in my case. Make sure you release all objects including range one! Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1]; worksheet.Paste(rng, false); releaseObject(rng); The options are together here.
The two dots rule did not work for me. In my case I created a method to clean my resources as follows: private static void Clean() { workBook.Close(); Marshall.ReleaseComObject(workBook); excel.Quit(); CG.Collect(); CG.WaitForPendingFinalizers(); }
My solution [DllImport("user32.dll")] static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId); private void GenerateExcel() { var excel = new Microsoft.Office.Interop.Excel.Application(); int id; // Find the Excel Process Id (ath the end, you kill him GetWindowThreadProcessId(excel.Hwnd, out id); Process excelProcess = Process.GetProcessById(id); try { // Your code } finally { excel.Quit(); // Kill him ! excelProcess.Kill(); }
You should be very careful using Word/Excel interop applications. After trying all the solutions we still had a lot of "WinWord" process left open on server (with more than 2000 users). After working on the problem for hours, I realized that if I open more than a couple of documents using Word.ApplicationClass.Document.Open() on different threads simultaneously, IIS worker process (w3wp.exe) would crash leaving all WinWord processes open! So I guess there is no absolute solution to this problem, but switching to other methods such as Office Open XML development.
The accepted answer did not work for me. The following code in the destructor did the job. if (xlApp != null) { xlApp.Workbooks.Close(); xlApp.Quit(); } System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL"); foreach (System.Diagnostics.Process process in processArray) { if (process.MainWindowTitle.Length == 0) { process.Kill(); } }
I am currently working on Office automation and have stumbled across a solution for this that works every time for me. It is simple and does not involve killing any processes. It seems that by merely looping through the current active processes, and in any way 'accessing' an open Excel process, any stray hanging instance of Excel will be removed. The below code simply checks for processes where the name is 'Excel', then writes the MainWindowTitle property of the process to a string. This 'interaction' with the process seems to make Windows catch up and abort the frozen instance of Excel. I run the below method just before the add-in which I am developing quits, as it fires it unloading event. It removes any hanging instances of Excel every time. In all honesty I am not entirely sure why this works, but it works well for me and could be placed at the end of any Excel application without having to worry about double dots, Marshal.ReleaseComObject, nor killing processes. I would be very interested in any suggestions as to why this is effective. public static void SweepExcelProcesses() { if (Process.GetProcessesByName("EXCEL").Length != 0) { Process[] processes = Process.GetProcesses(); foreach (Process process in processes) { if (process.ProcessName.ToString() == "excel") { string title = process.MainWindowTitle; } } } }
I think that some of that is just the way that the framework handles Office applications, but I could be wrong. On some days, some applications clean up the processes immediately, and other days it seems to wait until the application closes. In general, I quit paying attention to the details and just make sure that there aren't any extra processes floating around at the end of the day. Also, and maybe I'm over simplifying things, but I think you can just... objExcel = new Excel.Application(); objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); DoSomeStuff(objBook); SaveTheBook(objBook); objBook.Close(false, Type.Missing, Type.Missing); objExcel.Quit(); Like I said earlier, I don't tend to pay attention to the details of when the Excel process appears or disappears, but that usually works for me. I also don't like to keep Excel processes around for anything other than the minimal amount of time, but I'm probably just being paranoid on that.
As some have probably already written, it's not just important how you close the Excel (object); it's also important how you open it and also by the type of the project. In a WPF application, basically the same code is working without or with very few problems. I have a project in which the same Excel file is being processed several times for different parameter value - e.g. parsing it based on values inside a generic list. I put all Excel-related functions into the base class, and parser into a subclass (different parsers use common Excel functions). I didn't want that Excel is opened and closed again for each item in a generic list, so I've opened it only once in the base class and close it in the subclass. I had problems when moving the code into a desktop application. I've tried many of the above mentioned solutions. GC.Collect() was already implemented before, twice as suggested. Then I've decided that I will move the code for opening Excel to a subclass. Instead of opening only once, now I create a new object (base class) and open Excel for every item and close it at the end. There is some performance penalty, but based on several tests Excel processes are closing without problems (in debug mode), so also temporary files are removed. I will continue with testing and write some more if I will get some updates. The bottom line is: You must also check the initialize code, especially if you have many classes, etc.
Limiting Memory Leak on object not under your control
I have a method that was created by someone else and do not have access to the source. This Method creates a PDF document on the file system, but I'm having issues with memory when I start passing it 200+ images. From what I can tell it loads the object into memory in order to render the PDF, and then outputs to the file system. When I use a for or foreach loop to interate through my files and output them I can see in task manager the memory is building slowly, it does dip from time to time, but in general it's always on the way up. Here's my code: for(int i = 0; i < Documents.Count; i++) { IDocument document = Documents[i]; try { string exportFileName = string.Format("{0}_{1}_{2}_{3}", batchName, document.Field("NHS File\\SectionName").Text, document.Field("NHS File\\Classification Result").Text, dateTime); string imageFileName = exportFileName; int tries = 0; while (File.Exists(Path.Combine(processingLocation, imageFileName + ".pdf"))) { imageFileName = string.Format("{0}_{1}", exportFileName, tries++); } document.SaveAs((Path.Combine(processingLocation, imageFileName)), ExportOptions.GetImageOptions()); } catch(Exception ex) { Processing.ReportError(ex.ToString()); return; } finally { Marshal.ReleaseComObject(document); Marshal.FinalReleaseComObject(document); document = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); } } The issue is with the 'document.SaveAs();' I've tried calling the garbage collectors and release COM objects, adding and removing memory pressure, but nothing seems to work I continually get "System.Runtime.InteropServices.COMException (0x80004005): Not enough memory!" when it gets to 200 files. There seems to be no close(), Dispose(), or Finalise() options with this. Is there a way of calling this document.SaveAs() so that the memory used by the object is controlled and released by me? Thanks in advance.
If you have no control over the 3rd party code, you have little options to clean up the memory. The nature of the code seems to imply that it is heavy in COM Interop or platform invoke, which are a sure-fire way to leak memory if done incorrectly. GC.Collect doesn't collect natively allocated memory since it is outside of the garbage collector's knowledge. I would recommend putting your logic in a separate process, and when the processing is complete, letting the process exit. Exiting a process is a very reliable way to clean up resources and memory. Move your code into a console app, and use Process.Start to start your new command line utility from your current code. Use WaitForExit if you want to do is synchronously, and you can pass in arguments to the process such as the path of files or directories to process.
Release COM Object in C#
I know this has been discussed earlier but I couldn't find a satisfactory answer. I have an e-mail file (.msg) which I open like below and then call Display. oApp = new Microsoft.Office.Interop.Outlook.Application(); mail = (Microsoft.Office.Interop.Outlook.MailItem)oApp.Session.OpenSharedItem(fileName); mail.Display(false); oApp = null; // do I need to release this object ? users can close it and re-open it. Before they click "reopen" I check to see if the window still exists, if yes .. then just send a SetForeground(hwnd) to that window. if Not, that means user closed it so just release the mailItem object and open again. public static void ReleaseCOMObject(Microsoft.Office.Interop.Outlook.MailItem item) { int r = System.Runtime.InteropServices.Marshal.ReleaseComObject(item); while (r != 0) { r = System.Runtime.InteropServices.Marshal.ReleaseComObject(item); } } If I try to open the same file again. It "sometimes" throws a "file in use exception" So, I think even though I am releasing the MailItem .. its not been released properly. What can I do to ensure that its released properly. closing and reopening a file is a very common scenario. Any pointers will be very helpful.
If you're using .NET's COM-interop features (you are) then you shouldn't need to worry about this. COM tracks reference counts - and when the ref count reaches 0 COM objects get released automatically - and .NET takes care of working with the standard COM reference counting mechanism for you. If you were P/Invoking into a C library things might be different - but you shouldn't have any worries in a standard scenario like yours.
Doing things like this: mail = (Microsoft.Office.Interop.Outlook.MailItem)oApp.Session.OpenSharedItem(fileName); will cause the references to not be disposed of even when you call ReleaseComObject because the reference to the child object hasn't been disposed of properly. You should make the calls like this: session = oApp.Session; mail = (Microsoft.Office.Interop.Outlook.MailItem)session.OpenSharedItem(fileName); And you should dispose of each of these sub-objects, like session, in turn.
How to clean up COM references in .NET when app will be left running?
I am working on a .NET program that starts a new instance of Excel, does some work, then ends, but must leave Excel running. Later, when the program runs again, it will attempt to hook into the previous instance. What is the best way to handle the releasing of COM objects in this situation? If I do not do a "ReleaseComObject" on the app object the first time, then on the second run get the active object, then finally release the com object, do I have a memory leak? The following simplified code illustrates what I am trying to do: private Microsoft.Office.Interop.Excel.Application xlsApp; private Microsoft.Office.Interop.Excel.Workbook xlsWb; public void RunMeFirst() { //Start a new instance System.Type oSEType = Type.GetTypeFromProgID("Excel.Application"); xlsApp = Activator.CreateInstance(oSEType); xlsWb = xlsApp.Workbooks.Open("C:\\test1.xls"); //Do some stuff xlsWb.Close(false); Cleanup(ref xlsWb); //Do not quit Excel here //No Cleanup of xlsApp here? Is this OK? System.Environment.Exit(0); } public void RunMeSecond() { //Hook into existing instance xlsApp = Marshal.GetActiveObject("Excel.Application"); xlsWb = xlsApp.Workbooks.Open("C:\\test2.xls"); //Do some stuff xlsWb.Close(false); Cleanup(ref xlsWb); xlsApp.Quit(); Cleanup(ref xlsApp); System.Environment.Exit(0); } public void Cleanup(ref object theObj) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); Marshal.FinalReleaseComObject(theObj); theObj = null; } Thanks
In general, when working with the Office PIAs, i've found that these sorts of problems with objects not being released arise when you have instance variables that hold COM objects. In your case, these would be xlsApp and xlsWb. You don't have to quit the Excel application in order to release the objects, but you do have to perform the following as a cleanup procedure: Marshal.FinalReleaseComObject(xlsWb); xlsWb = null; Marshal.FinalReleaseComObject(xlsApp); xlsApp = null; GC.Collect(); Local-scoped variables containing COM objects don't seem to cause this problem, only instance variables. I hope this helps!
I'm a tad confused - at the end of RunMeFirst you exit the process, so is RunMeSecond run in a different process from the first method or the same process? Either way I would change your code so that xlsWb is scoped locally and just do the following: public void RunMeFirst() { System.Type oSEType = Type.GetTypeFromProgID("Excel.Application"); xlsApp = Activator.CreateInstance(oSEType); Workbook xlsWb = xlsApp.Workbooks.Open("C:\\test1.xls"); // Do stuff xlsWb.Close(false); System.Environment.Exit(0); } You shouldn't really call any of the ReleaseComObject methods except in certain circumstances (for example in server applications where many COM objects will all be in use and so it is critical to release objects as soon as possible). The usual mechanism for cleaning up COM object should simply be to let them go out of scope, in which case the COM object will be released using the same magic the GC uses (I believe that it is cleaned up as the finalizer is run, but I'm not 100% sure on this). The reason why its a good idea to scope xlsWb locally (rather than as a class member or a static member) is that class members will only be cleaned up when the class is cleaned up, and static members are never cleaned up until the appdomain is unloaded. If you do need to clean up a COM object referenced by a static field then the way to do this is to set the static field to null so the underlying COM object can be cleaned up by the GC scoping rules: xlsWb = null; Also there should be no real need to call GC.Collect for similar reasons - Mike Rosenblum gives a fair explanation as to why he's calling GC.Collect and ReleaseComObject, but no real justification as to why he isn't just satisfied with letting the garbage collector do its job. Like I said, there are situations where you might want more control over the release of COM objects, however these are the exception not the rule. You may also find reading Marshal.ReleaseComObject Considered Dangerous useful.
Does *every* Excel interop object need to be released using Marshal.ReleaseComObject?
Edit Please see also How do I properly clean up Excel interop objects?. I recently came across this question, and it provided a lot of insight into the problem of how to properly dispose of COM objects. Definitely check beyond the first (marked) answer, because the other answers go beyond the simple "don't use two dots" and "use ReleaseComObject for every com object" advice. I revisited this question in the first place because I realized that, despite being very thorough about registering and disposing all my COM objects, my Excel instances still weren't being properly disposed. It turns out, there are ways COM objects can be created that are completely non-obvious (i.e., you can miss COM objects even if you never use two dots). In addition, even if you are thorough, if your project grows beyond a certain size, the chance of missing a COM object approaches 100%. And it can be very hard to find the one you missed when that happens. The answers to the question linked above provide some other techniques for making sure the Excel instance definitely gets closed. Meanwhile, I've made a small (but significant) update to my ComObjectManager (below) to reflect what I learned from the question linked above. Original Question I've seen several examples where Marshal.ReleaseComObject() is used with Excel Interop objects (i.e., objects from namespace Microsoft.Office.Interop.Excel), but I've seen it used to various degrees. I'm wondering if I can get away with something like this: var application = new ApplicationClass(); try { // do work with application, workbooks, worksheets, cells, etc. } finally { Marashal.ReleaseComObject(application) } Or if I need to release every single object created, as in this method: public void CreateExcelWorkbookWithSingleSheet() { var application = new ApplicationClass(); var workbook = application.Workbooks.Add(_missing); var worksheets = workbook.Worksheets; for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++) { var worksheet = (WorksheetClass)worksheets[worksheetIndex]; worksheet.Delete(); Marshal.ReleaseComObject(worksheet); } workbook.SaveAs( WorkbookPath, _missing, _missing, _missing, _missing, _missing, XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing); workbook.Close(true, _missing, _missing); application.Quit(); Marshal.ReleaseComObject(worksheets); Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(application); } What prompted me to ask this question is that, being the LINQ devotee I am, I really want to do something like this: var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name); ...but I'm concerned I'll end up with memory leaks or ghost processes if I don't release each worksheet (ws) object. Any insight on this would be appreciated. Update Based on the answers so far, it sounds like I really do need to release every single com object I create. I took the opportunity to build a ComObjectManager class to make it a little easier to deal with this headache. You have to remember to use the Get() method each time you instantiate a new com object, but if you do, it will take care of everything else for you. Please let me know if you see any problems with it (or edit and leave a comment if you are able). Here's the code: public class ComObjectManager : IDisposable { private Stack<object> _comObjects = new Stack<object>(); public TComObject Get<TComObject>(Func<TComObject> getter) { var comObject = getter(); _comObjects.Push(comObject); return comObject; } public void Dispose() { // these two lines of code will dispose of any unreferenced COM objects GC.Collect(); GC.WaitForPendingFinalizers(); while (_comObjects.Count > 0) Marshal.ReleaseComObject(_comObjects.Pop()); } } Here's a usage example: public void CreateExcelWorkbookWithSingleSheet() { using (var com = new ComObjectManager()) { var application = com.Get<ApplicationClass>(() => new ApplicationClass()); var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing)); var worksheets = com.Get<Sheets>(() => workbook.Worksheets); for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++) { var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]); worksheet.Delete(); } workbook.SaveAs( WorkbookPath, _missing, _missing, _missing, _missing, _missing, XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing); workbook.Close(true, _missing, _missing); application.Quit(); } }
I believe you would have to call ReleaseComObject on each COM object. Since they're not garbage-collected, the parent-child hierarchy doesn't really come into the equation: even if you release the parent object it does not decrement the reference count on any child objects.
You should call Marshal.ReleaseComObject on every COM object you use in your code, not just the main application object.
No. You don't have to release a single COM object. See this answer: Clean up Excel Interop Objects with IDisposable To sum up the answer: The garbage collector will take care of them when it feels like it except if your program crash. What you need to be aware is: Running your app in DEBUG mode might delay/prevent the cleanup of COM object. Stopping the debugger (from within visual studio) will prevent the clean up of COM object. It is as if you crashed the app. If you close the debugged app properly, you will see that all COM objects are released. Also, running your app in Release mode and closing it properly will also release all COM objects. Now if you want to release all COM object right after your method call ended, then you can Simply call GC.Collect(); GC.WaitForPendingFinalizers(); But you need to call this OUTSIDE the method who created the COM object. Again, this might not work as expected if you are debugging the app but it will work in Release mode.