代码之家  ›  专栏  ›  技术社区  ›  chiccodoro

使用包装对象正确清理Excel Interop对象

  •  11
  • chiccodoro  · 技术社区  · 15 年前

    所有这些问题:

    解决C在使用Excel COM对象后不能正确释放这些对象的问题。围绕这个问题,主要有两个工作方向:

    1. 在不再使用Excel时终止Excel进程。
    2. 注意先显式地将每个COM对象分配给一个变量,并确保最终对每个对象执行marshal.releaseComObject。

    有些人说2太繁琐了,在代码的某些地方是否忘记遵守这个规则总是存在一些不确定性。我仍然觉得1很脏,容易出错,而且我猜在一个受限的环境中试图杀死一个进程可能会引发安全错误。

    所以我一直在想 通过创建另一个模仿Excel对象模型的代理对象模型(对我来说,它足以实现我实际需要的对象)。原则如下:

    • 每个ExcelInterop类都有其代理,该代理包装该类的对象。
    • 代理在其终结器中释放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 回复  |  直到 13 年前
        1
  •  5
  •   Community Mohan Dere    8 年前

    在析构函数中执行ReleaseComObject是否不可能/不好/危险?(我只看到了将其放入Dispose()而不是析构函数的建议-为什么?)

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

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

    如果您希望尝试显式地清理,则“不要将两个点与COM对象一起使用”准则将帮助您记住保留对所创建的每个对象的引用,以便在完成后清理这些对象。

        2
  •  2
  •   Codezy    15 年前

    我们使用了在msdn杂志中描述的LifeTimeScope类。使用它可以正确地清理对象,并对我们的Excel导出非常有效。代码可以在这里下载,还包含杂志文章:

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

        3
  •  1
  •   TcKs    15 年前

    看看我的项目 MS Office for .NET . 通过原生vb.net后期绑定功能解决了referencich包装器对象和原生对象的问题。

        4
  •  0
  •   Rotaerk    15 年前

    我要做什么:

    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))
        {
            ...
        }
    
        5
  •  0
  •   Mark    13 年前

    为了它的价值, 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);
            }
        }
    
    推荐文章