关于c#:使用Wrapper对象正确清理excel互操作对象

Using Wrapper objects to Properly clean up excel interop objects

所有这些问题:

  • Excel2007在通过.NET关闭时挂起
  • 如何正确清理C中的ExcelInterop对象#
  • 如何正确清理C中的互操作对象#

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

  • 在不再使用Excel时终止Excel进程。
  • 注意先显式地将每个COM对象分配给一个变量,并确保最终对每个对象执行marshal.releaseComObject。
  • 有些人说2太繁琐了,在代码的某些地方是否忘记遵守这个规则总是存在一些不确定性。我仍然觉得1很脏,容易出错,而且我猜在一个受限的环境中试图杀死一个进程可能会引发安全错误。

    因此,我一直在考虑通过创建另一个模拟Excel对象模型的代理对象模型来解决2问题(对我来说,它足以实现我实际需要的对象)。原则如下:

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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()而不是终结器的建议-为什么?)
    • 如果这个方法有意义,有什么改进的建议吗?

    0

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

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

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


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

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


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


    就其价值而言,codeplex上的Excel刷新服务使用以下逻辑:

    2


    我要做什么:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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);
    }

    用例。请注意要退出的调用,这似乎是使进程结束所必需的:

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