使用Wrapper对象正确清理excel互操作对象

时间:2010-05-04 17:09:39

标签: c# excel interop com-interop

所有这些问题:

解决了C#在使用后没有正确释放Excel COM对象的问题。解决这个问题主要有两个方向:

  1. 不再使用Excel时终止Excel进程。
  2. 注意首先将用于变量的每个COM对象显式分配,并保证最终在每个COM对象上执行Marshal.ReleaseComObject。
  3. 有些人表示2太繁琐,而且在代码中的某些地方忘记遵守此规则总是存在一些不确定性。仍然1对我来说似乎很脏并且容易出错,我想在有限的环境中试图杀死进程可能会引发安全错误。

    所以我一直在考虑通过创建另一个模仿Excel对象模型的代理对象模型来解决问题2(对我来说,实现我真正需要的对象就足够了)。原则如下:

    • 每个Excel Interop类都有自己的代理,用于包装该类的对象。
    • 代理在其终结器中释放COM对象。
    • 代理模仿Interop类的接口。
    • 最初返回COM对象的任何方法都会更改为返回代理。其他方法只是将实现委托给内部COM对象。

    示例:

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

    我特别向您提出的问题:

    • 谁发现这是一个坏主意,为什么?
    • 谁发现这是一个可怕的想法?如果是这样,为什么没有人实施/发布这样的模型呢?是仅仅是因为努力,还是我错过了这个想法的杀戮问题?
    • 在终结器中执行ReleaseCOMObject是不可能/不好/容易出错的? (我只看到过将它放在Dispose()而不是终结器中的建议 - 为什么?)
    • 如果方法有意义,有什么建议可以改进吗?

5 个答案:

答案 0 :(得分:5)

  

在析构函数中执行ReleaseCOMObject是不可能/坏/危险的? (我只看到过将它放在Dispose()而不是析构函数中的建议 - 为什么?)

建议不要将清理代码放在终结器中,因为与C ++中的析构函数不同,它不是确定性调用的。在对象超出范围后不久可能会调用它。可能需要一个小时。它可能永远不会被调用。通常,如果要处置非托管对象,则应使用IDisposable模式而不是终结器。

您链接到的solution尝试通过显式调用垃圾收集器并等待终结器完成来解决该问题。实际上这并不是一般的推荐,但是对于这种特殊情况,有些人认为它是一种可接受的解决方案,因为很难跟踪所有创建的临时非托管对象。但明确清理是正确的方法。然而,考虑到这样做的困难,这种“黑客”可能是可以接受的。请注意,此解决方案可能比您提出的想法更好。

如果您想尝试明确清理,“不要在COM对象中使用两个点”指南将帮助您记住保留对您创建的每个对象的引用,以便您可以在清理时清除它们'完了。

答案 1 :(得分:2)

我们使用MSDN杂志中描述的LifetimeScope类。使用它可以正确清理对象,并且可以很好地处理Excel导出。代码可以在这里下载,也包含杂志文章:

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

答案 2 :(得分:1)

查看我的项目MS Office for .NET。通过本机VB.NET后期绑定能力解决了参考包装对象和本机对象的问题。

答案 3 :(得分:0)

我要做什么:

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

用法案例。注意调用Quit,这似乎是使进程结束所必需的:

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit()))
using (var workbooks = CleanupComObject(excel.Object.Workbooks))
    {
        ...
    }

答案 4 :(得分:0)

对于它的价值,Excel Refresh Service on codeplex使用了这个逻辑:

    public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
    {
        if (reference == null) return;
        try
        {
            doThis(reference);
        }
        finally
        {
            Marshal.ReleaseComObject(reference);
        }
    }