views:

341

answers:

4

All of these questions:

struggle with the problem that C# does not release the Excel COM objects properly after using them. There are mainly two directions of working around this issue:

  1. Kill the Excel process when Excel is not used anymore.
  2. Take care to explicitly assign each COM object used to a variable first and to guarantee that eventually, Marshal.ReleaseComObject is executed on each.

Some have stated that 2 is too tedious and there is always some uncertainty whether you forget to stick to this rule at some places in the code. Still 1 seems dirty and error-prone to me, also I guess that in a restricted environment trying to kill a process could raise a security error.

So I've been thinking about solving 2 by creating another proxy object model which mimics the Excel object model (for me, it would suffice to implement the objects I actually need). The principle would look as follows:

  • Each Excel Interop class has its proxy which wraps an object of that class.
  • The proxy releases the COM object in its finalizer.
  • The proxy mimics the interface of the Interop class.
  • Any methods that originally returned a COM object are changed to return a proxy instead. The other methods simply delegate the implementation to the inner COM object.

Example:

public class Application
{
    private Microsoft.Office.Interop.Excel.Application innerApplication
        = new Microsoft.Office.Interop.Excel.Application innerApplication();

    ~Application()
    {
        Marshal.ReleaseCOMObject(innerApplication);
        innerApplication = null;
    }

    public Workbooks Workbooks
    {
        get { return new Workbooks(innerApplication.Workbooks); }
    }
}

public class Workbooks
{
    private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks;

    Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks)
    {
        this.innerWorkbooks = innerWorkbooks;
    }

    ~Workbooks()
    {
        Marshal.ReleaseCOMObject(innerWorkbooks);
        innerWorkbooks = null;
    }
}

My questions to you are in particular:

  • Who finds this a bad idea and why?
  • Who finds this a gread idea? If so, why hasn't anybody implemented/published such a model yet? Is it only due to the effort, or am I missing a killing problem with that idea?
  • Is it impossible/bad/error-prone to do the ReleaseCOMObject in the finalizer? (I've only seen proposals to put it in a Dispose() rather than in a finalizer - why?)
  • If the approach makes sense, any suggestions to improve it?
+2  A: 

Is it impossible/bad/dangerous to do the ReleaseCOMObject in the destructor? (I've only seen proposals to put it in a Dispose() rather than in a destructor - why?)

It is recommended not to put your clean up code in the finalizer because unlike the destructor in C++ it is not called deterministically. It might be called shortly after the object goes out of scope. It might take an hour. It might never be called. In general if you want to dispose unmanaged objects you should use the IDisposable pattern and not the finalizer.

This solution that you linked to attempts to work around that problem by explicitly calling the garbage collector and waiting for the finalizers to complete. This is really not recommended in general but for this particular situation some people consider it to be an acceptable solution due to the difficulty of keeping track of all the temporary unmanaged objects that get created. But explicitly cleaning up is the proper way of doing it. However given the difficulty of doing so, this "hack" may be acceptable. Note that this solution is probably better than the idea you proposed.

If instead you want to try to explicitly clean up, the "don't use two dots with COM objects" guideline will help you to remember to keep a reference to every object you create so that you can clean them up when you're done.

Mark Byers
You are right with the finalizer in principal, but in this particular situation I might say: Let's delegate responsibility to the garbage collector, since due to the finalizer I can rely on the GC also releasing the COM object as soon as the internal object is destroyed. If I want to explicitely release all resources at some place on the code, I could use GC.Collect().
chiccodoro
@chiccodoro: The problem with this method is that it's relying on an implementation detail of GC. If the GC changes slightly it may no longer work. But I understand the difficulty of doing it properly in this situation, and that the hack may be the most cost-effective solution in some situations.
Mark Byers
Hmm. I see your point. But using a using() for each single dot is tedious. Maybe only define a Dispose() method on the Excel.Application which runs GC.Collect() so that all other Excel objects are released, too, at this point? For until the Excel.Application leaves the "using" scope, I don't require my program to release COM resources which stem from the Application.
chiccodoro
@chiccodoro: You could create a wrapper class which gives you access to an Excel.Application object and in the Dispose of this class do the GC.Collect trick.
Mark Byers
+1  A: 

We use the LifetimeScope class that was described in the MSDN magazine. Using it properly cleans up objects and has worked great with our Excel exports. The code can be downloaded here and also contains the magazine article:

http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266

Codezy
Hi Codezy, thanks for the interesting link.
chiccodoro
cool linkz thanks mate.
Anonymous Type
A: 

What I'd do:

class ScopedCleanup<T> : IDisposable where T : class
{
    readonly Action<T> cleanup;

    public ScopedCleanup(T o, Action<T> cleanup)
    {
        this.Object = o;
        this.cleanup = cleanup;
    }

    public T Object { get; private set; }

    #region IDisposable Members

    public void Dispose()
    {
        if (Object != null)
        {
            if(cleanup != null)
                cleanup(Object);
            Object = null;
            GC.SuppressFinalize(this);
        }
    }

    #endregion

    ~ScopedCleanup() { Dispose(); }
}

static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class
{
    return new ScopedCleanup<T>(o, cleanup);
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class
{
    return
        CleanupObject(
            comObject,
            o =>
            {
                if(actionBeforeRelease != null)
                    actionBeforeRelease(o);
                Marshal.ReleaseComObject(o);
            }
        );
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class
{
    return CleanupComObject(comObject, null);
}

Usage case. Note the call to Quit, which seems to be necessary to make the process end:

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit()))
using (var workbooks = CleanupComObject(excel.Object.Workbooks))
    {
        ...
    }
Rotaerk
Hi Rotaerk. This looks like an interesting enhancement of the IDisposable approach, but it doesn't answer my question. (Or then I miss the point.)
chiccodoro
As has been mentioned, using IDisposable is the way to go, not the finalizer. Finalizers will make sure the resource is cleaned up, but give you no control over when. GC.Collect doesn't guarantee that everything will be collected immediately, so it is not the solution. A disposable would give you strict control over when the release occurs. Furthermore, the using-statement lets you do this in an exception-safe manner. (Just remember when debugging not to stop your app prematurely before the cleanup, or it won't be done, and you'll end up having a bunch of Excel processes running.)
Rotaerk
The reason you are seeing left over objects is because you HAVENT called .WaitForPendingFinalizers() in between calls to Collect() its that simple.
Anonymous Type
+1  A: 

Look at my project MS Office for .NET. There is solved problem with referencich wrapper objects and native objects via native VB.NET late-binding ability.

TcKs
Hi TcKs. Can you expand on that? In the project page you write: "MS Office Primary Interop Assemblies is not required.". Does that mean that one don't need to directly communicate its object model because the wrapper objects take care of, or does it rather mean that you did a quite low-level implementation which replaces the interop assembly completely?
chiccodoro
Yes, it workarounds the standard way of interopassembly. It inspects COM object model dynamicaly at runtime. It is littlebit slower than PIA but is independent of version (future version included).
TcKs
That sounds real interesting. -- You last changed the code and web page in 2008?
chiccodoro
The common goals can be done by current or older versions. I plane revitalize project when Office 2010 will come out.
TcKs