Sample app did not properly cleanup objects ошибка

DxWnd

Window hooker to run fullscreen programs in window and much more…

Status: Beta

Brought to you by:
ghotik

  • Summary

  • Files

  • Reviews

  • Support

  • Home

  • Discussion

Menu

Grand Prix World


Created:

2013-06-07

Updated:

2023-03-01

  • Marc

    Thanks for the ideas.
    Here’s a picture of the desktop, although if you were asking about the problem of painting in on the wrong screen, I’d fixed that issue (see post below) and this now shows the painting inside the proper window frame. Before, the part that should have been within the frame was appearing on the other monitor with the same (or close) 50,50 offset from the top-left of that display. So, the frame was on the main window (right side of this image) while the painting was occuring on the left side.

    The image does show what the race mode looks like now.

    When I tried the two flags (Lock/unlock pitch fix and Width no power of 2) together, the overall graphics of the game were scrambled on stop and I had to kill the program. The Lock/unlock pitch fix flag alone does the same, while the Width no Power of 2 flag alone runs the program but has the same look to the race screen as in the screen shot. The Share ddraw and GDI DC option alone had the same look also.

  • Marc

    OK, got a step closer.

    First, I went back to the file for GPW that comes in the ‘exports’ folder. Left the windows explorer compat settings alone.

    Then, I found a checkbox that says «Don’t Fix Pixel format» under the DirectX tab and checking that makes the ‘paint’ occur inside the window on my main monitor. So, one issue down. The race is still unreadable due to color or other issues, but now I seem to be down to one issue. :)

  • Anonymous

    Hi Marc, this method is worth a try. Go to Direct3D tab > uncheck «Fix ddraw refcount» and check «Return 0 refcount». Does that make the problem less serious?

    If this method not work, please revert to previous setting.

    • Marc

      Pretty much the same. Here’s a pic, but it looks a lot like the one I just cleared ouf of Paint.

      Thumbnail

  • gho

    I saw the picture, and it is strange indeed. This sort of things usualy happens when the program doesn’t manage the graphic in the proper way (that is using the system calls) but takes some shortcut for supposed optimization and generates a full mess.
    Usually the problem disappear if the program works in the exact resolution he supposes to be. Could you try to set the window heigth and width values to 0, 0 in the main DxWnd conf panel and see if the problem disappear?

    • Marc

      OK, here goes then. I’ve got the check box back to «fix ddraw refcount’, and now the H and W set to 0.

      With game programmers, especially from that era, it wouldn’t surprise me at all if they were doing some shortcuts. Strange, a game like this they shouldn’t have had to, but the game is littered with these useless 3d views of race cars on a race track so they may have done something to get more speed in there.

      Had to wait for it, Program was slower to appear. Then had trouble getting the mouse to work. But in the end, it got to the race mode and no difference.

      At this point, I think I’m still close to the shipped settings for GPW, with the changes put in to test and then removed once they don’t work. But, here’s a export of my latest version.

      BTW, I think 800 x 600 was the correct size (values in H and W before I tried 0 and 0). I think I saw that somewhere in display settings while messing around with this in fullscreen (no dxWnd) mode.

      Thanks for the help and for giving it a try. :)

      • gho

        What happens if you temporarily turn off your secondary monitor and configure the desktop accordingly? I’s like to be sure the problem depends on the two monitors, and this test is pretty easy to do.

  • Gianmarco Marzo

    I fear that I need help.
    I tried running GPW through DXWnd following the steps listed in this guide: http://www.gprm.co.uk/forums/index.php?topic=5835.0

    But I alternatively get these two error messages:

    1 «sample app did not clean up objects»
    2 (much more frequent) «%%%sdevices. Try enabling reference rasterizer by running refrast.reg» (a file that does not appear to exist on my computer).
    What’s going on? T_T

  • gho

    Hi Gianmarco,
    I’ m replying from mobile phone but it’ s not easy.
    I’ll be back from holyday twesday, I will make tests then.
    Be patient and happy Easter.

  • Gianmarco Marzo

  • gho

    Here I am. back to work.
    The problem may depend on the version of the game. What I’m actually supporting is a variation of the windows XP port of the game, «gpwxp2.exe», that I can’t even remember how exactly I got it.
    In any case, I would make a clean start putting both you and me in identical conditions: so, please
    1. download the latest DxWnd release v2.03.58,
    2. import the included file «Grand Prix World.dxw» from the DxWnd export folder
    3. unzip the attached version of the game in the game folder.
    4. adjust the dxwnd path field in the configuration so that it points to the gpwxp2.exe file.
    At this point, If it doesn’t work, at least we will share equal conditions.

  • Gianmarco Marzo

    Hello Gho,
    thank you. I will try this out in the afternoon as soon as I am back home and tell you what happens.
    Hope you had pleasant holidays.

  • Gianmarco Marzo

    Okay Gho,
    I tried. I think we’ve made progress. It now says «ensure that game CD is inserted». I suppose I need a no-CD patch of some kind.

    • gho

      Uhm…. no, I think that the problem is that you didn’t install the full gpwxp patch, that likely copies more resourced on hard disk and makes the CD useless. I provided just the exe, but this is not enough.
      Have a look at here: http://www.simracingworld.com/files/download/1086-grand-prix-world-windows-xp-patch/ and follow the instructions. In any case, keep the gpwxp2.exe that I sent you because it may work better with DxWnd (Oh, my! I can’t remember why I patched it!)

  • Gianmarco Marzo

    Hello Gho,
    I downloaded the patch you linked, and kept your exe, but I still get the no cd issue.

    • gho

      What OS are you trying to run it on? I heard that some copy protection schemas were left unsupported by MS recently, and maybe with the protection, also the crack was gone …
      In any case I had it working on WinXP, Win7 and Win10. I left untested only Win8 and 8.1.
      More likely, there must be some error in the installation, but from here I can hardly tell….

       

      Last edit: gho 2016-04-02

  • Gianmarco Marzo

    I am running it under windows 8.1.
    I tried with a pirated version of the game. It’s hard to find a legitimate copy and I would be hesitant to buy it when it may very well not run at all. :/

    • gho

      I never had the (mis)fortune to verify this, but the general opinion on Win8.1 is not very good, since it gained on the field the nickname of «Game killer»!
      You should have a proposal from MS for a free upgrade to Win10, and even if you don’t, there should be a way to di that for free. My personal suggestion is to migrate to Win10: I also was a bit reluctant, but then I migrated directly from Win7 to Win10 in a single step and it was absolutely ok: you loose nothing (as far as I can tell) and you gain some disk space (strange, but true), a more efficient OS and a better back-compatibility with everything, including an integrated support to emulate 8 or 16 bit color depth in a window (they must have copied something from DxWnd, I dare say…).
      In any case, I’m afraid I can hardly support you on 8.1, since I don’t have it. The only final attempt could be to try the last DxWnd beta that I updloaded a few hours ago: that manages much better some latest Window versions.

  • Gianmarco Marzo

    Thank you gho, you have been very kind and I sincerely appreciate that.
    The upgrade to Windows 10 sounds nice. I will consider it, first I want to verify what the web says about the other games I play often running under 10, but since they’re mostly recent I don’t think it’s going to be a problem.
    I will come back to you as soon as I have upgraded to 10 and let’s see if that fixes things.

    • gho

      Release v2.03.60 fixed many problems about ddraw method hooking, that has become more and more selective from WinXP up to Win10. If you like, you may also try this last version, there’s a slight possibility of improvement.

  • Veedub

    Hey Gho, the OP back again :)

    For future tests, can you please test Grand Prix World with DxWnd and gpw103b.exe? Basically, while gpwxp.exe was repackaged by the developer to work with Windows XP, it isnt the latest official version of the game. The latest official version is gpwv101b (packaged for Win98), and I managed to patch out the WinXP bugs and get it working with Windows XP eventually (creating the unofficial gpwv103b.exe). So as a community, we want to use gpw103b going forward where possible.

    You will need to install gpwv101b first, and then apply gpwv103b (which is also a no-cd exe).
    gpwv101b: http://www.edwardgrabowski.com/downloads/gpwv1.zip
    gpwv103b: http://s000.tinyupload.com/index.php?file_id=66902834389965582469

    Of course for GPW to run on any system, you should set compatibility mode on the gpwxxxx.exe file to Windows 98 to get past the game freezing at the gold FIA logo.

    I’m hoping you can help us with the following problem a user has raised to my attention:

    • Unfortunately the 103b patch doesn’t run on Windows 10 but apparently gpwxp2.exe does
    • The same error messages that came up in that Sourceforge thread appear —
    • ‘Sample App Did Not Properly Clean Up Objects’ if you use XP Compatibility mode and
    • ‘Try enabling the reference rasterizer by executing EnableRefRast.reg’ if you use Windows98 compatibility mode.

    I suspect these errors relate to when the game initialises and tests the hardware/software graphics capabilities. As it is Windows 10, I have very little knowledge on this issue but I’m hoping you may understand what is causing these checks to fail or how to bypass them. Apparently the gpwxp2.exe that you created at the start of the thread does seem to work with Windows 10, so it may just be possible to get gpw103b.exe working as well. I did a binary diff on gpwxp.exe, and your exes gpwxp2.exe and gpwxp3.exe to note your changes and will see if I can carry those changes across to gpwv103b.exe (I’ll have to fire up idapro, and cross reference the code) but I suspect there may be more issues to solve. The two versions (gpw101b.exe and gpwxp.exe) have quite different disassembly layouts but the same start up logic.

    Thank you for your efforts so far, any help appreciated. I’ll let you know if I make any discoveries, once I get my own Win10 box up and running, hopefully one day soon.

    Also, not DxWnd related but it is graphics related, can anyone/graphics experts explain this issue with flickering graphics? From the ~3:30 mark in the video, the UI starts to flicker. This is when playing the game normally at full screen on any system/OS. Some system configurations cause the flicker, others work perfectly fine. Our suspicions are that its graphics hardware/driver related (possibly nvidia only). I wonder if it is GDI vs DirectX interference or something or clockspeed, as I notice the system that the game flickers on has unhelpfully super responsive button clicks?

    Youtube video: https://www.youtube.com/watch?v=0glTWBjZHXk&t=3m30s
    Google suggestion: http://community.pcgamingwiki.com/topic/811-flickering-in-old-games-on-nvidia-very-strange-problem/

    Regards, Veedub

    • gho

      Ok, I’ll do my best.
      Unfortunately MS changed a lot of things in these last years and, despite the declaration of Win10 as the last Window version, it has a dynamic patching feature (shims) that makes things changing (usually for the better…) almost every day, but this way you can never say you’re done wirh a game!
      One very positive thing is that now Win95/98 emulation is no longer made through a virtual machine, then it is now compatible with DxWnd and the two mechanisms (Windows compatibility assistant and DxWnd) can help each other to reach the goal.
      Wait for news (and if they don’t come, remind me every now and then….)
      bye

  • gho

    I compared the dxwnd logs of gpwxp2 and your gpwv103b, and the cause of the ‘Sample App Did Not Properly Clean Up Objects’ error message is the lack of Release upon the backbuffer surface, that is also linked with two references to the directdraw session and inhibit the resource freedom (see picture).
    The error message can be easily eliminated setting the DxWnd «return 0 refcount» instead of «fix ddraw refcount», but this is just a fake 0 number returned to the application while the resources still have references, and I suspect that this causes the block of the application right after that.
    I attach also the full log files, in case they could help to localize the code that needs a fix.

     

    Last edit: gho 2016-05-07

    Thumbnail

  • gho

    Bad news: in the attempt to trick the program and make a workaround, I made a sperimental version of DxWnd that counts (and logs) the DirectDraw4::Release() methods, so I found that the missing call is after the 19th cycle and I modified again DxWnd so that it doubles the Release call when the counter reaches 19.
    The result is that the error message no longer appears, the logs show that the game go further ahead, but it keeps stopping on the splash screen (or maybe it is not stopped, but the output operations are not visible?).
    In any case, it seems that the missing Release operation is not your only and main problem!
    In attach the crazy dll that makes the trick, just in case you want to see what changes (nothing at all).

  • Veedub

    Thank you for your efforts so far Gho — at least it gives me some leads to go on when I get to analysing the code. In v1.03b i have freed up a lot of bytes in the exe to write new asm, so potentially (if i’m clever enough) I can rewrite the start up sequence and see what I can eliminate or copy over from gpwxp.exe, and get something working in Windows 10. :) We dont have many clues from the developers as to what they actually changed between gpw.exe (v1.00) and gpwxp.exe to make it XP compatible but as far as I could tell it was recompiled with a newer version of Visual C++ (upgraded from v4 to v5) and probably had minor code changes, so that could be the subtle difference.


Log in to post a comment.

Unity Discussions

Loading

Overview

When running a project in the editor using Holographic Simulation, on stopping the project it throws an error (see below). I can’t tell if this error has any residual effect, but Holographic Simulation is extremely buggy for me (slow to close, freezes, crashes sometimes) so possible it is related.

Expected Behavior

No errors.

Actual Behavior

Error:
Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)
The following scene GameObjects were found:
Spatial Awareness System
WindowsMixedRealitySpatialMeshObserver

Steps to reproduce

(Links to sample github project preferred)
This happens to me with a, more or less, default project, using latest vNext code.

Unity Editor Version

2018.3.6f1

Mixed Reality Toolkit Release Version

vNext

  • Home
  • Question

  • How do I properly clean up Excel interop objects?

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?

This question is related to
c#
excel
interop
com-interop

The answer is


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

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

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

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


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


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


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

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.


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.


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.


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

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

Here is a really easy way to do it:

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

int objExcelProcessId = 0;

Excel.Application objExcel = new Excel.Application();

GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);

Process.GetProcessById(objExcelProcessId).Kill();

My answer is late and its only purpose is to support the solution proposed by Govert, Porkbutts, and Dave Cousineau with a complete example. Automating Excel or other COM objects from the COM-agnostic .NET world is a “tough nut,” as we say in German, and you can easily go nuts. I rely on the following steps:

  1. For each interaction with Excel, get one and only one local instance ExcelApp of the Application interface and create a scope within which ExcelApp lives. This is necessary, because the CLR won’t free the resources of Excel before any reference to Excel goes out of scope. A new Excel process is started in the background.

  2. Implement functions that do the tasks by using ExcelApp to
    generate via Collection properties new objects like Workbook(s),
    Worksheet(s), and Cell(s). In these functions don’t care for the
    voodoo one-dot-good, two-dot-bad rule, don’t try to get a reference
    for each implicitly created object and don’t
    Marshall.ReleaseComObject anything. That is the job of the Garbage
    Collection.

  3. Within the scope of ExcelApp, call these functions and pass the reference ExcelApp.

  4. While your Excel instance is loaded, don’t allow any user actions that would bypass the Quit function that unloads this instance again.

  5. When you are done with Excel, call the separate Quit function within the scope made for Excel handling. This should be the last statement within this scope.

Before you run my app, open the task manager and watch in the Processes tab the entries in background processes. When you start the program, a new Excel process entry appears in the list and stays there until the thread wakes up again after 5 seconds. After that the Quit function will be called, stopping the Excel process that gracefully vanishes from the list of background processes.

using System;
using System.Threading;
using Excel = Microsoft.Office.Interop.Excel;

namespace GCTestOnOffice
{
  class Program
  {
    //Don't: private  static Excel.Application ExcelApp = new Excel.Application();

    private static void DoSomething(Excel.Application ExcelApp)
    {
        Excel.Workbook Wb = ExcelApp.Workbooks.Open(@"D:\Aktuell\SampleWorkbook.xlsx");
        Excel.Worksheet NewWs = Wb.Worksheets.Add();

        for (int i = 1; i < 10; i++)
        {
            NewWs.Cells[i, 1] = i;
        }
        Wb.Save();
    }

    public static void Quit(Excel.Application ExcelApp)
    {
        if (ExcelApp != null)
        {
            ExcelApp.Quit(); //Don't forget!!!!!
            ExcelApp = null;
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    static void Main(string[] args)
    {
      {
        Excel.Application ExcelApp = new Excel.Application();
        Thread.Sleep(5000);
        DoSomething(ExcelApp);
        Quit(ExcelApp);
        //ExcelApp goes out of scope, the CLR can and will(!) release Excel
      }

      Console.WriteLine("Input a digit: ");
      int k = Console.Read(); 
    }
  }
}

If I changed the Main function to

static void Main(string[] args)
{
  Excel.Application ExcelApp = new Excel.Application();
  DoSomething(ExcelApp);

  Console.WriteLine("Input a digit: ");
  int k = Console.Read(); 
  Quit(ExcelApp);
}

the user could instead of inputting a number, hit the Close button of the console and my Excel instance lived happily ever after. So, in cases where your Excel instance remains stubbornly loaded, your cleanup feature might not be wrong, but is bypassed by unforeseen user actions.

If the Program class would have a member for the Excel instance, the CLR would not unload the Excel instance before the app terminates. That’s why I prefer local references that go out of scope when they are no longer needed.


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.


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


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

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


Of the three general strategies considered in other answers, killing the excel process is clearly a hack, whereas invoking the garbage collector is a brutal shotgun approach meant to compensate for incorrect deallocation of COM-objects. After lots of experimentation and rewriting the management of COM objects in my version-agnostic and late-bound wrapper, I have come to the conclusion that accurate and timely invocations of Marshal.ReleaseComObject() is the most efficient and elegant strategy. And no, you do not ever need FinalReleaseComObject(), because in a well-writtin program each COM acquired on once and therefore requires a single decrement of the reference counter.

One shall make sure to release every single COM object, preferably as soon as it is no longer needed. But it is perfectly possible to release everything right after quitting the Excel application, at the only expense of higher memory usage. Excel will close as expected as long as one does not loose or forget to release a COM object.

The simplest and most obvious aid in the process is wrapping every interop object into a .NET class implementing IDisposable, where the Dispose() method invokes ReleaseComObject() on its interop object. Doing it in the destructor, as proposed in here, makes no sense because destructors are non-deterministic.

Show below is our wrapper’s method that obtains a cell from WorkSheet bypassing the intermediate Cells member. Notice the way it disposes of the intermediate object after use:

public ExcelRange XCell( int row, int col)
{   ExcelRange anchor, res;
    using( anchor = Range( "A1") )
    {   res = anchor.Offset( row - 1, col - 1 );  }
    return res;
}

The next step may be a simple memory manager that will keep track of every COM object obtained and make sure to release it after Excel quits if the user prefers to trade some RAM usage for simpler code.

Futher reading

  1. How to properly release Excel COM objects,
  2. Releasing COM objects: Garbage Collector vs. Marshal.RelseaseComObject.

I had this same problem getting PowerPoint to close after newing up the Application object in my VSTO AddIn. I tried all the answers here with limited success.

This is the solution I found for my case — DONT use ‘new Application’, the AddInBase base class of ThisAddIn already has a handle to ‘Application’. If you use that handle where you need it (make it static if you have to) then you don’t need to worry about cleaning it up and PowerPoint won’t hang on close.


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?


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…



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

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

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.


This is the only way that really works for me

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

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?


Solution 1

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


Solution 2

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


Solution 3

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


Solution 4

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

Solution 5

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.


Solution 6

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.


Solution 7

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.


Solution 8

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/


Solution 9

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


Solution 10

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.


Solution 11

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


Solution 12

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.


Solution 13

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

Solution 14

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.


Solution 15

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

Solution 16

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

Solution 17

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

Solution 18

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


Solution 19

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


Solution 20

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.


Solution 21

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.


Solution 22

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

Solution 23

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.


Solution 24

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

Solution 25

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

Solution 26

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.


Solution 27

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

Solution 28

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

Solution 29

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.


Solution 30

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.

Понравилась статья? Поделить с друзьями:
  • Sampfuncs ошибка при запуске
  • Samp ошибка fastman92 limit adjuster
  • Samp ошибка cannot find 1536x864x32 video mode
  • Samp ошибка bex
  • Samp unable to execute ошибка