views:

16020

answers:

13

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.

Anyone realize what I am doing wrong, or has an alternative to ensure interop objects are properly disposed of.

Thanks.

+5  A: 

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 that every reference to an Excel COM object had to be set to null when you have finished with it. This includes Cells, Sheets, everything.

Philip Fourie
nice! clean and easy!
eviljack
+61  A: 

Excel does not quit because your app is still holding references to COM objects.

I guess you're invoking members of a COM object without assigning it to a variable.

For me it was the excelApp.Worksheets object I directly used without assigning it to a variable:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

What I didn't know was 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 2 dots with com objects.


So with this knowledge the right way of doing the above is:

Worksheets sheets = excelApp.Worksheets;
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
VVS
Never use 2 dots with COM objects - great rule of thumb, thanks.
HAdes
Yeah, saved my life (at least an evening ;)
VVS
Unfortunately, I find that this policy is almost impossible to utilize properly 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...
Mike Rosenblum
... and there would be no way at all to know where in the code you had such a mistake. You'd have to scan *ALL* your code by hand and hope you find them all. For anyone reading this, I would strongly consider reading my full reply on this below, and then decide for yourself.
Mike Rosenblum
Then I suggest not using Excel from COM and save yourself all of the trouble. The Excel 2007 formats can be used without ever opening Excel, gorgeous.
sixlettervariables
I did not understand what "two dots" mean. Can you please explain?
A9S6
This means, you shouldn't use the pattern comObject.Property.PropertiesProperty (you see the two dots?). Instead assign comObject.Property to a variable and use and dispose that variable.A more formal version of the above rule could be sth. like "Assign com object to variables before you use them. This includes com objects that are properties of another com object."
VVS
I was not aware that I even had to close the worksheets I had created. worksheet = null removed the two Excel instances that I started. Great thread - appreciate it :)
sonstabo
Since sheet is a child of sheets, wouldn't that be released first?
Nick Hebb
@Nick: Actually, you don't need any kind of cleanup, since the garbage collector will do it for you. The only thing you need to do is to assign every COM object to its own variable so the GC knows of it.
VVS
+3  A: 

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.

MagicKat
How aobut xlRange.Interior.Color for example.
HAdes
Interior needs to be release (its in the namespace)... Color on the other hand doesn't (cause its from System.Drawing.Color, iirc)
MagicKat
actually Color is a Excel color not a .Net color. you just pass a Long.Also workbooks def. need to be released, worksheets... less so.
Anonymous Type
A: 

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, 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.

bill_the_loser
A: 

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.

Joe
+37  A: 

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#.

Mike Rosenblum
A related discussion can be found on the ExtremeVBTalk .NET Office Automation forum, here: http://www.xtremevbtalk.com/showthread.php?t=303928.
Mike Rosenblum
And if all else fails, then Process.Kill() can be used (as a last resort) as described here: http://stackoverflow.com/questions/51462/killing-excelexe-on-server#312513
Mike Rosenblum
+1  A: 

Damn, I really should finish my series one of these days:

http://ausdotnet.wordpress.com/2008/05/15/com-interop-welcome-to-pain/

no use of GC.Collect() waitforpendingFinalizers allowed? your dreamin' bro.
Anonymous Type
I wish I understood what you meant...
A: 

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

+2  A: 

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/

Edward Wilde
yup this one is good. I think this code can be updated though now that FinalReleaseCOMObject is available.
Anonymous Type
+6  A: 

The top two responses are actually very good, but they don't cover 1 major problem, a complete application crash/failure.

If you've already created an Excel process, and your application crashes prematurely then your back to the same problem. You'll have an Excel process running lose.

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 a solution, which is actually written in C#.

Closing an Excel Interop process cleanly, even if your application crashes

Basically, the solution was 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.

PS - Sorry, I am limited to 1 link as a new user. I actually had a ling to an MSDN article explaining Windows "Jobs".

ShortRound
+3  A: 

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.

Mohsen Afshin
+14  A: 

There are different ways and advices 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:
    http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects-in-c/158752#158752

  • Calling GC.Collect() and GC.WaitForPendingFinalizers() to make CLR release unused com-objects

  • 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 offense 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.

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 exception if the Process could not be killed (for example the process doesn't exist anymore), and another method throws exception if the Process was not killed. The only weak place in this code is security permissions. Theoretically, 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! :)

nightcoder
sad that the thread is already so old that your excellent answer appears that far below, which I think is the only reason for it not being upvoted more times...
chiccodoro
thanx, chiccodoro
nightcoder
Boo Yah! It's gone! I'm watching the task manager to see if Excel drops off the planet and it did. Thanks many!
Chris
So instead of cleaning your mess up, you just burn the house? Yeah, bad analogy..
VVS
@VVS:It's absolutely incorrect analogy. And your "rule of 2 dots" doesn't always work. Far not always.
nightcoder
@nightcoder: Do you have a sample? Reminds me to http://www.codinghorror.com/blog/2008/03/the-first-rule-of-programming-its-always-your-fault.html
VVS
I can find a sample, but I don't want to waste my time and argue with you. Many people confirm that ReleaseComObject() doesn't always help.
nightcoder
I have to admit, when I first read your answer, I thought it was a giant kludge. After about 6 hours of wrestling with this (everything is released, I've got no double dots, etc..), I now think your answer is genius.
Mark
Thanks for this. Been wrestling with an Excel that wouldn't close no matter what for a couple of days before I found this. Excellent.
BBlake
+1  A: 

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.

Chris McGrath