By HAdes


2008-10-01 17:18:43 8 Comments

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?

30 comments

@nightcoder 2009-12-12 14:50:15

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

@chiccodoro 2010-05-04 16:32:12

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

@nightcoder 2010-08-24 08:40:17

@VVS: It's absolutely incorrect analogy. And your "rule of 2 dots" doesn't always work. Far not always.

@VVS 2010-08-24 12:56:33

@nightcoder: Do you have a sample? Reminds me to codinghorror.com/blog/2008/03/…

@nightcoder 2010-08-24 19:46:29

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.

@Mark 2010-08-27 15:53:52

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.

@BBlake 2010-10-19 15:27:40

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.

@devuxer 2011-11-02 01:21:42

I'm not noticing any issue using GC.Collect() and GC.WaitForPendingFinalizers() in Debug mode...if I do just your second step ("wrap the method working with Excel in a separate method"), it seems to work fine. In fact, Excel disappears nicely from my Task Manager process list even if I never use Marshal.ReleaseComObject(). If using either of your methods (KillProcessByMainWindowHwnd or GC.Collect() and GC.WaitForPendingFinalizers() with a wrapped method), isn't manually releasing com objects completely unnecessary?

@nightcoder 2011-11-02 16:41:54

DanM, if do not kill Excel's process and use GC.Collect() + GC.WaitForPendingFinalizers then you still need to call Marshal.ReleaseComObject() and set null to COM object wrappers (like workbook objects, etc.), otherwise Excel process will not be closed. If you kill Excel's process I would still call Marshal.ReleaseComObject() and set null to COM object wrappers, because it will help free memory sooner and it's more correct (for example, if you don't call ReleaseComObject() then those wrappers will try to access nonexistent com objects that were already destroyed when you killed Excel process).

@devuxer 2011-11-02 17:29:42

@nightcoder, thanks for your reply. Let me make sure we're talking about the same thing. Here's what I tried: (1) put all code dealing with Excel COM objects into a self-contained method (or hierarchy of methods), (2) create a calling method that invokes the self-contained method in a try block, then calls GC.Collect() and GC.WaitForPendingFinalizers() in a finally block. In my testing so far, this clears the Excel process from Task Manager every time. Step 1 ensures that COM objects are stored only in local variables, so once the method completes, there should be no live references.

@devuxer 2011-11-02 17:39:01

@nightcoder (continued), You seem to be saying that this may not be reliable if I don't also manually call Marshal.ReleaseComObject() for certain COM object wrappers. If GC.Collect() and GC.WaitForPendingFinalizers() are really not enough (again, assuming that all references to COM objects are restricted to no-longer-in-scope local variables), (1) why? and (2) which Excel COM objects need to be released? In my test case, I am not releasing the following objects with no apparent ill effects: Application, Workbook, Sheets, Worksheet, and Range. Thanks again!

@Mike Rosenblum 2011-12-29 18:10:09

@nightcoder: awesome answer, really detailed. Your comments with respect to debug mode are very true and important that you pointed this out. The same code in release mode can often be fine. Process.Kill however, is only good when using automation, not when your program is running within Excel, e.g., as a managed COM add-in.

@Mike Rosenblum 2011-12-29 18:14:25

@DanM: Your approach is 100% correct and will never fail. By keeping all your variables local to the method, all references are effectively unreachable by .NET code. This means that when your finally section calls GC.Collect(), the finalizers for all your COM objects will get called with certainty. Each finalizer then calls Marshal.FinalReleaseComObject on the object being finalized. Your approach is therefore simple and fool-proof. Have no fear using this. (Only caveat: if using VSTO, which I doubt that you are, you would need to call GC.Collect() & GC.WaitForPendingFinalizers TWICE.)

@devuxer 2011-12-29 23:20:02

@MikeRosenblum, thanks for responding to my comment. I appreciate the reassurance. For the particular project I was working on at the time, I ended up bypassing the issue altogether by using a file-based approach (using EPPlus) instead of COM. If I need to actually use COM interop again, though, I'll know what to do.

@LostSalad 2014-05-28 14:34:39

I think this takes 'leaky abstraction' to the extreme

@MickyD 2015-03-16 02:29:27

Explicitly killing or posting a WM_CLOSE to an out-of-process COM server (that might be serving other COM clients) is a dreadful idea. If you are resorting to this it's because your COM protocol is broken. COM servers should not be treated as a regular windowed-app

@Kajiyama 2015-03-16 17:47:08

Huh, I tried like 5 different approaches, how to fix this issue.. but this one.. this one was the only one, that is working for me! This answer deserves more upvotes

@Pikachu620 2018-07-10 02:03:10

I tried to implement your code but it told me A namespace cannot directly contain members such as fields or methods. Could you PLEASE teach me how to set it up correctly, please!? Appreciated!!!

@miriyo 2018-07-23 16:17:32

I used the first Hwnd method which worked a treat for me thank you. I couldn't get your other more attractive approach to work for me.

@SamSar 2019-01-04 19:26:25

Tested with Microsoft Excel 2016

A really tested solution.

To C# Reference please see: https://stackoverflow.com/a/1307180/10442623

To VB.net Reference please see: https://stackoverflow.com/a/54044646/10442623

1 include the class job

2 implement the class to handle the apropiate dispose of excel proces

@Aloha 2018-12-19 03:06:10

There i have an idea,try to kill the excel process you have opened:

  1. before open an excelapplication,get all the process ids named oldProcessIds.
  2. open the excelapplication.
  3. get now all the excelapplication process ids named nowProcessIds.
  4. when need to quit,kill the except ids between oldProcessIds and nowProcessIds.

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    

@VVS 2008-10-01 17:30:41

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

@Mike Rosenblum 2008-10-29 22:17:00

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 2008-10-29 22:19:28

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

@user7116 2009-12-18 17:35:40

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.

@A9S6 2010-01-27 13:18:03

I did not understand what "two dots" mean. Can you please explain?

@VVS 2010-02-04 10:37:03

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

@sonstabo 2010-02-11 11:42:55

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

@CtrlDot 2010-08-18 14:35:52

Since sheet is a child of sheets, wouldn't that be released first?

@VVS 2010-10-18 07:42:34

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

@CodingBarfield 2011-08-11 08:28:46

@VSS thats bollocks, the GC cleans up everything since the wrapper variables are made by the .net framework. It just might take forever for the GC to clean it up. Calling GC.Collect after heavy interop isn't a bad idear.

@l--''''''---------'''''''''''' 2012-04-01 22:11:10

i tried your solution and getting same problem

@VVS 2012-04-03 08:03:07

@I__: Since I don't know your code I can only guess that you missed a COM object (a dot) somewhere.

@l--''''''---------'''''''''''' 2012-04-03 15:41:09

@vvs thank you for your help. what does a dot somewhere mean?

@David Brunelle 2012-11-13 19:52:57

Wow.. This can make up pretty long code for nothing though. So I cannot do worksheet(strrange).value2. I must define the range THEN assign the value to make sure everything is ok ?

@Coops 2012-11-26 12:41:20

@sixlettervariables but how can you write to excel without COM, do you have reference URL?

@user7116 2012-11-26 16:44:23

@Coops 2012-11-27 09:02:54

@sixlettervariables Ah writing an XML that seems a little more code to build the XML but much simpler overall in terms of not dealing with COM objects =)

@Sandy Gifford 2014-09-19 14:49:42

I just loudly groaned "OOOOOOOOOOOOOOOOOOOOOOOOOOOH" in the middle of my office. I've never really understood that two-dots thing until now. Thanks!

@AjV Jsy 2014-10-08 15:01:22

Thanks. I had a situation where all the advice didn't seem to work until I tried a combo of being extremely careful to avoid "double dotting", with using a group of If objWhatever IsNot Nothing Then System.Runtime.InteropServices.Marshal.FinalReleaseComObject‌​(objWhatever) (VB) in reverse order. I then found that manually invoking the GC wasn't required as, at last, EXCEL was no longer hanging around. The looming last resort Dim NewestExcelProcess = Process.GetProcessesByName("EXCEL").OrderByDescending(Functi‌​on(p) p.StartTime).First()/NewestExcelProcess.Kill() approach wasn't needed.

@user3141326 2014-10-09 20:29:43

Is there a typo in the answer or am I missing something? According to this stackoverflow answer we need to release com objects in the reverse order. Doesn't this mean we need to reverse the order of Marshal.ReleaseComObject(sheets); and Marshal.ReleaseComObject(sheet); in the answer?

@whytheq 2016-06-06 19:15:39

@VVS if the xlsm file, which is part of my automated process, references another xlsm "factory" file will this cause the creation of further hidden com objects ?

@Joe Gayetty 2018-04-18 19:14:47

Hans Passant disagrees with this answer here: stackoverflow.com/a/25135685/3852958 Not saying he is right. Just saying he disagrees...and his opinion carries some weight.

@Maxter 2019-02-28 17:36:42

You should post your Post Mortem Update at the top of the answer. It turns out Hans Passant solution is perfect. Make the real solution more visible.

@BTownTKD 2013-03-28 14:12:39

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.

@Caio Proiete 2015-12-03 02:20:46

This should be marked as the answer, really. NetOffice abstracts all this complexity away.

@Stas Ivanov 2017-02-10 08:34:30

I've been using NetOffice for a considerable amount of time writing an excel add-in and it worked perfectly. The only thing to consider is that if you don't dispose used objects explicitly, it will do it when you exit the application (because it keeps track of them anyway). So the rule of thumb with NetOffice is always to use "using" pattern with every Excel object like cell, range or sheet etc.

@Бурда Евгений 2017-03-10 04:18:09

'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

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).'

@Hermes Monteiro 2017-02-14 07:55:42

This is the only way that really works for me

        foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
        {
            proc.Kill();
        }

@Antoine Meltzheim 2013-06-14 15:16:58

¨°º¤ø„¸ 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);
}

@Amit Mittal 2012-06-25 06:52:30

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

@Govert 2016-06-29 22:50:23

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.

@Alan Baljeu 2018-10-13 15:13:04

Virtually ALL ANSWERS ON THIS PAGE ARE WRONG EXCEPT THIS ONE. They work but are way too much work. IGNORE the "2 DOTS" rule. Let the GC do your work for you. Supplementary evidence from .Net GURU Hans Passant : stackoverflow.com/a/25135685/3852958

@akirakonenu 2015-05-04 13:12:33

So far it seems all answers involve some of these:

  1. Kill the process
  2. Use GC.Collect()
  3. Keep track of every COM object and release it properly.

Which makes me appreciate how difficult this issue is :)

I have been working on a library to simplify access to Excel, and I am trying to make sure that people using it won't leave a mess (fingers crossed).

Instead of writing directly on the interfaces Interop provides, I am making extension methods to make live easier. Like ApplicationHelpers.CreateExcel() or workbook.CreateWorksheet("mySheetNameThatWillBeValidated"). Naturally, anything that is created may lead to an issue later on cleaning up, so I am actually favoring killing the process as last resort. Yet, cleaning up properly (third option), is probably the least destructive and most controlled.

So, in that context I was wondering whether it wouldn't be best to make something like this:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

I used 'Releasible' to avoid confusion with Disposable. Extending this to IDisposable should be easy though.

An implementation like this:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

And one could do something similar for all sorts of COM objects.

In the factory method:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var excel = new ApplicationContainer().Releasible;
        excel.Visible = !hidden;

        return excel;
    }

I would expect that every container will be destructed properly by the GC, and therefore automatically make the call to Quit and Marshal.FinalReleaseComObject.

Comments? Or is this an answer to the question of the third kind?

@Eric K. 2017-02-21 08:47:34

application.Show() not working

@craigtadlock 2014-02-25 03:35:56

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

@D.G. 2013-11-18 22:58:12

After trying

  1. Release COM objects in reverse order
  2. Add GC.Collect() and GC.WaitForPendingFinalizers() twice at the end
  3. No more than two dots
  4. Close workbook and quit application
  5. 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();
    }
}

@Carol 2016-03-01 21:26:39

I found I did not need to nullify ComObjects, only Quit() - Close() and FinalReleaseComObject. I can't believe this is all that was missing on my end to get it to work. Great!

@Shivam Srivastava 2013-08-27 10:11:24

Use:

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Declare it, add code in the finally block:

finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (excelApp != null)
    {
        excelApp.Quit();
        int hWnd = excelApp.Application.Hwnd;
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        Process[] procs = Process.GetProcessesByName("EXCEL");
        foreach (Process p in procs)
        {
            if (p.Id == processID)
                p.Kill();
        }
        Marshal.FinalReleaseComObject(excelApp);
    }
}

@Blaz Brencic 2013-04-11 14:01:14

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.

@Porkbutts 2013-01-11 01:46:22

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.

@Rachel Hettinger 2013-07-30 17:51:17

Note that the referenced msdn article pertains to Office 2003...

@Arvand 2012-03-14 15:47:35

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.

@quixver 2011-06-19 00:16:53

Excel is not designed to be programmed via C++ or C#. The COM API is specifically designed to work with Visual Basic, VB.NET, and VBA.

Also all the code samples on this page are not optimal for the simple reason that each call must cross a managed/unmanaged boundary and further ignore the fact that the Excel COM API is free to fail any call with a cryptic HRESULT indicating the RPC server is busy.

The best way to automate Excel in my opinion is to collect your data into as big an array as possible / feasible and send this across to a VBA function or sub (via Application.Run) which then performs any required processing. Furthermore - when calling Application.Run - be sure to watch for exceptions indicating excel is busy and retry calling Application.Run.

@Dominic Zukiewicz 2012-05-02 10:16:00

C# and VB.NET all run under the CLR,so it can't be designed for VB.NET and not C#. They are the ultimately the same thing, just with different language semantics for building an application.

@quixver 2012-05-17 18:24:22

Please try calling the Run method from the excel Application object in C# ( ver 3.0 and earlier) or C++. Then try doing the same from VB.net. After the 10th or 12th missing parameter you pass in - you will probably realize what I meant :]

@quixver 2012-05-17 18:27:28

Also - like you mentioned - C# and VB.net are both CLR languages. They offer programmers varying subsets of the CLR's functionality along with various syntactic sugar. It just so happens that the subset offered by VB.net makes com and excel programming easier.

@Dominic Zukiewicz 2012-05-17 19:33:07

But again, thats just semantics of the language (prior to v3.5). I do indeed feel the pain of Application.Run(Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,,Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value); which VB has had, even before .NET. The managed libraries of Microsoft.Office.Tools DLL makes it easy peesy to do all this stuff without any COM cleanup at all, as its all managed wrapper.

@Jeremy Thompson 2012-09-27 04:59:25

Excel is not designed to be programmed via C++ or C# - is incorrect information.

@quixver 2013-01-19 16:47:13

@Jeremy - it's true though. It's primarily designed to be programmed via late bound clients like VBA and VB. Using c++ or c# to work with Excel is a nightmare.

@Jeremy Thompson 2013-01-20 07:01:17

We can agree to disagree, IMHO its far from a nightmare. Using C# .Net 4.0 with optional parameter support is practically the same as VB.Net. In my experience more companies are porting old VBA code to C# rather than VB.Net.

@bill_the_loser 2008-10-01 17:47:22

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.

@MagicKat 2008-10-01 17:45:45

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.

@HAdes 2008-10-01 17:48:57

How aobut xlRange.Interior.Color for example.

@MagicKat 2008-10-01 17:56:49

Interior needs to be release (its in the namespace)... Color on the other hand doesn't (cause its from System.Drawing.Color, iirc)

@Anonymous Type 2010-03-02 21:16:51

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.

@Philip Fourie 2008-10-01 17:30:36

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.

@Tom Brearley 2016-08-02 14:32:38

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

@dicksters 2016-04-07 12:29:30

Just to add another solution to the many listed here, using C++/ATL automation (I imagine you could use something similar from VB/C#??)

Excel::_ApplicationPtr pXL = ...
  :
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;

This works like a charm for me...

@Mike Rosenblum 2008-10-01 19:54:21

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 2009-01-24 15:15:38

A related discussion can be found on the ExtremeVBTalk .NET Office Automation forum, here: xtremevbtalk.com/showthread.php?t=303928.

@Mike Rosenblum 2009-02-07 18:56:29

And if all else fails, then Process.Kill() can be used (as a last resort) as described here: stackoverflow.com/questions/51462/…

@Mike Rosenblum 2011-02-13 17:22:34

Glad it worked Richard. :-) And here is a subtle example where simply avoiding "two dots" is not sufficient to prevent a problem: stackoverflow.com/questions/4964663/…

@Mike Rosenblum 2011-04-24 02:00:00

Here's an opinion on this from Misha Shneerson of Microsoft on the topic, within the comment date Oct. 7, 2010. (blogs.msdn.com/b/csharpfaq/archive/2010/09/28/…)

@Brett Green 2015-05-19 18:01:17

I'm hoping this helps a persistent problem we're having. Is it safe to do the garbage collection and release calls within a finally block?

@primo 2015-07-01 10:55:15

Worked for me, but only after moving the GC.Collect() after the Marshal.FinalReleaseComObject(xlApp). GC.WaitForPendingFinalizers() was unnecessary.

@xaisoft 2017-07-12 14:09:00

@MikeRosenblum - Hi, I have an add in that reads from the active sheet and then I use Interop to create a new excel file and write to that one. I do not want to close it when the application is finished. I want the user to close it. In this case, do I still need to call FinalReleaseComObject

@Martin 2014-09-05 11:42:45

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

@Martin 2014-09-05 11:47:26

@Loart 2014-06-18 09:34:16

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

@Dave Cousineau 2011-08-31 21:18:17

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

@andrew.cuthbert 2015-02-26 06:51:02

This answer works and is infinitely more convenient than the accepted answer. No worries about "two dots" with COM objects, no use of Marshal.

@Hahnemann 2013-12-12 22:37:19

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

@joshgo 2009-08-20 16:00:37

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

@MickyD 2015-03-16 03:32:09

Explicitly killing an out-of-process COM server (that might be serving other COM clients) is a dreadful idea. If you are resorting to this it's because your COM protocol is broken. COM servers should not be treated as a regular windowed-app

@Ned 2012-08-08 18:38:33

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.

@Coops 2012-11-26 13:39:40

Very good point! I think because I am using Office 2007 there is some clean-up done on my behalf. I have used the advise further up but have not stored variables as you have suggested here and EXCEL.EXE does exit but I could just be lucky and if I have any further problems I will definitely look at this part of my code =)

Related Questions

Sponsored Content

26 Answered Questions

[SOLVED] How do I enumerate an enum in C#?

42 Answered Questions

[SOLVED] How to create Excel (.XLS and .XLSX) file in C# without installing Ms Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 980938 View
  • 1696 Score
  • 42 Answer
  • Tags:   c# .net excel file-io

41 Answered Questions

[SOLVED] Deep cloning objects

  • 2008-09-17 00:06:27
  • NakedBrunch
  • 728612 View
  • 1995 Score
  • 41 Answer
  • Tags:   c# .net clone

1 Answered Questions

[SOLVED] c# excel does not clean up after creating pdf file

0 Answered Questions

Excel interop in C# will not actually release no matter what I do

1 Answered Questions

2 Answered Questions

Alternatives to Excel Interop SaveAs() method

5 Answered Questions

[SOLVED] Using Wrapper objects to Properly clean up excel interop objects

0 Answered Questions

Clean Up Resources Excel Interop with C#

  • 2010-12-03 17:04:57
  • Adam Rackis
  • 307 View
  • 0 Score
  • 0 Answer
  • Tags:   c# office-interop

5 Answered Questions

[SOLVED] C# .net Excel Interop leaves Excel process hanging

  • 2010-06-22 21:03:55
  • Rick Harby
  • 4837 View
  • 1 Score
  • 5 Answer
  • Tags:   c# excel interop

Sponsored Content