2

I'm writing a small library to help manage some objects in Excel. I'm testing this DLL using a simple console application that makes calls to the library, then prints the results out. I can then end the program in any of the typical fashions, usually by either hitting return (thus completing the ReadLine call) or hitting the window's close button. However, the reference to the Excel instance behaves differently based on how the program exits.

In my program, if no existing reference to Excel can be found, I use the following line:

_app = new ExcelInterop.Application();

where _app is an instance of Microsoft.Office.Interop.Excel.Application in either a static or singleton class (I've tried both, both have the same results).

Assuming the program creates it's own instance (and doesn't find one already open):

  • The instance will remain open if the program is exited by clicking the close window button:
  • The instance is released, and no longer appears in the task manager if the program is exited by reaching the end of the code in the Main block

Is there anyway to make all program ends behave like the latter case? Furthermore, this DLL will go on to be used in a WPF application, are there similar concerns in WPF? Or at large, even?

Perhaps most importantly, what are the technical reasons for this behavior?

Sandy Gifford
  • 7,219
  • 3
  • 35
  • 65
  • 1
    maybe do your tidy up on an close event like in this http://stackoverflow.com/questions/474679/capture-console-exit-c-sharp – jgok222 Jul 30 '14 at 16:33
  • @jgok222 That seems like a pretty good conclusion, but do you have any idea what exactly is happening here under the hood? The most confusing part is that I haven't written any clean-up code at all at this point. I was fully expecting the instance to remain open in all cases. I guess I feel a little uneasy pushing code that I don't fully understand. – Sandy Gifford Jul 30 '14 at 16:37

1 Answers1

3

A console mode program is a pretty hostile place for the COM interop wrapper objects that are needed for an apartment-threaded out-of-process COM server. This program demonstrates the issue:

using System;

class Program {
    static void Main(string[] args) {
        var prg = new Program();
        Console.ReadLine();
    }
    ~Program() {
        Console.WriteLine("Clean-up completed");
        System.Threading.Thread.Sleep(1500);
    }
}

Try it both ways, by pressing Enter and by clicking the Close button. You'll see that the finalizer never gets executed. The operating system terminates the process before it gets a chance to shut down properly when you click the Close button.

Same problem with the finalizers for the COM wrappers. They cannot execute so IUnknown::Release() doesn't get called and the Office program is completely unaware that the client program is no longer there. Windows has its own cleanup for abandoned out-of-process servers but that doesn't work for Office programs for some otherwise mysterious reason.

That explains it, fixing it isn't so easy. You'll have to register a callback that runs when the Close button is clicked. If necessary, set the app reference to null if it is still in scope and force the finalizer to run with GC.Collect() + GC.WaitForPendingFinalizers(). Do keep in mind that this is just a band-aid, not a fix. It won't work when the user aborts your program while you are busy talking to the Office program. Avoiding a console mode project is best.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Excellent answer! Exactly what I was looking for. Is the close button case unique to console applications? Is this something I'd have to worry about in WPF? The application this will be a part of makes good use of WPF's close events. – Sandy Gifford Jul 30 '14 at 17:34
  • 1
    GUI apps don't have this problem. – Hans Passant Jul 30 '14 at 17:37
  • Awesome, in that case I think I'll make a public cleanup method, and let any console developers (which I doubt there'll ever be any of) handle the event listeners for their own program. – Sandy Gifford Jul 30 '14 at 17:42
  • @SandyGifford The standard for the "public cleanup method" is implementing the `IDisposeable` interface and putting your cleanup code in there. Then users of your class can put the instance of your wrapper either in their own `using` statement or make their class `IDisposeable` too and dispose of it there. – Scott Chamberlain Jul 31 '14 at 02:11