Using Wrapper objects to Properly clean up excel interop objects

佐手、 提交于 2019-12-04 01:21:29
Mark Byers

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.

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

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.

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

For what it's worth, the Excel Refresh Service on codeplex uses this logic:

    public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
    {
        if (reference == null) return;
        try
        {
            doThis(reference);
        }
        finally
        {
            Marshal.ReleaseComObject(reference);
        }
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!