【问题标题】:COM object excel interop clean upCOM 对象 excel 互操作清理
【发布时间】:2012-03-25 10:29:37
【问题描述】:

假设我有一个组件正在使用 Workbook 对象做某事,并且在该方法主体的中间某处我调用了另一个类的某个方法。 例如:

public class MainComponent
{

    public void MyMainMethod()
    {
       OtherComponent otherComponent = new OtherComponent();
       Workbook document;
       // some work with workbook object

       // working with document and worksheet objects.
       otherComponent.MethodCall(document);

       // some work with workbook object and it's worksheets.

       foreach(Worksheet sheet in document.Workheets)
         // do something with sheet
    }
}

public class OtherComponent
{
  public void MethodCall(Workbook document)
  {
    string worksheetNames = "";
    foreach(Worksheet sheet in document.Worksheets)
      worksheetNames += sheet.Name;
    Console.WriteLine(worksheetNames);
  }
}

在那个 otherComponent.MethodCall(document);我正在使用文档,并且正在遍历它的工作表。

编辑更具体的问题。我是否应该在 otherComponent.MethodCall(document) 中的文档和工作表上调用 ReleaseCOMObject?

我从来没有对我应该如何管理这个非托管代码有任何好的解释。 如果有人能向我解释这一点,我将不胜感激。

【问题讨论】:

  • 一般来说,创建对象的方法应该负责清理。在这种情况下,“清理”的组成部分有些模糊。您应该发布初始化和清理代码,以及对您所做的“可能会在以后引起问题”的更多解释。

标签: c# com excel-interop


【解决方案1】:

您必须在创建它们的范围内手动释放所有本地对象。通过自动化使用 Office 应用程序时,不要依赖垃圾收集器来清理这些对象 - 即使它做对了,它也可能需要一些时间才能启动,并且您最终可能会得到持有对其他对象的引用的临时对象你认为已经消失了。

This is a somewhat related question 提供更多详细信息,如果您尝试在隐藏 Excel 的情况下从应用程序运行 Excel,可能适用于您。

与您绝对相关的部分是:

  • 将每个使用 Excel 的函数封装在 try..catch 块中以捕获任何可能的异常。
  • 始终通过调用Marshal.ReleaseComObject() 显式释放所有Excel 对象,然后在不需要它们时立即将变量设置为null。始终在 finally 块中释放这些对象,以确保失败的 Excel 方法调用不会导致 COM 对象悬空。
  • 发生错误时,请关闭您正在使用的 Excel 实例。您不太可能从与 Excel 相关的错误中恢复,并且您保留实例的时间越长,它使用资源的时间就越长。
  • 当您退出 Excel 时,请确保保护该代码免受递归调用的影响 - 如果您的异常处理程序在您的代码已在关闭 Excel 的过程中尝试关闭 Excel,您最终会得到一个死掉的 Excel实例。
  • 在调用Application.Quit() 方法后立即调用GC.Collect() 和GC.WaitForPendingFinalizers() 以确保.NET Framework 立即释放所有Excel COM 对象。

编辑:这是在您为问题添加更多详细信息之后。

在otherComponent 中,您不需要释放Workbook 和Document 对象。这两个对象是在您的第一个对象中创建的,这意味着第一个对象是所有者。由于它是拥有顶级 Excel 对象的第一个对象(假设您在某处还有一个 Application 对象),您的第一个对象可以调用 otherComponent,传入 Workbook 和 Document,然后返回,清理他们起来。如果您从未在 MainComponent 中使用任何这些对象,那么您应该在 otherComponent 中创建与 Excel 相关的对象并在那里清理它们。

使用 COM 互操作,您应该在靠近您需要它们的位置创建您的 COM 对象,并尽快明确地释放它们。对于 Office 应用程序尤其如此。

我制作这个类是为了更容易使用 COM 对象:这个包装器是一次性的,因此您可以将 using(...) 与您的 COM 对象一起使用 - 当 using 范围结束时,包装器会释放 COM 对象。

using System;
using System.Runtime.InteropServices;

namespace COMHelper
{
    /// <summary>
    /// Disposable wrapper for COM interface pointers.
    /// </summary>
    /// <typeparam name="T">COM interface type to wrap.</typeparam>
    public class ComPtr<T> : IDisposable
    {
        private object m_oObject;
        private bool m_bDisposeDone = false;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="oObject"></param>
        public ComPtr ( T oObject )
        {
            if ( oObject == null )
                throw ( new ArgumentNullException ( "Invalid reference for ComPtr (cannot be null)" ) );

            if ( !( Marshal.IsComObject ( oObject ) ) )
                throw ( new ArgumentException ( "Invalid type for ComPtr (must be a COM interface pointer)" ) );

            m_oObject = oObject;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="oObject"></param>
        public ComPtr ( object oObject ) : this ( (T) oObject )
        {
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~ComPtr ()
        {
            Dispose ( false );
        }

        /// <summary>
        /// Returns the wrapped object.
        /// </summary>
        public T Object
        {
            get
            {
                return ( (T) m_oObject );
            }
        }

        /// <summary>
        /// Implicit cast to type T.
        /// </summary>
        /// <param name="oObject">Object to cast.</param>
        /// <returns>Returns the ComPtr object cast to type T.</returns>
        public static implicit operator T ( ComPtr<T> oObject )
        {
            return ( oObject.Object );
        }

        /// <summary>
        /// Frees up resources.
        /// </summary>
        public void Dispose ()
        {
            Dispose ( true );
            GC.SuppressFinalize ( this );
        }

        /// <summary>
        /// Frees up resurces used by the object.
        /// </summary>
        /// <param name="bDispose">When false, the function is called from the destructor.</param>
        protected void Dispose ( bool bDispose )
        {
            try
            {
                if ( !m_bDisposeDone && ( m_oObject != null ) )
                {
                    Marshal.ReleaseComObject ( m_oObject );
                    m_oObject = null;
                }
            }
            finally
            {
                m_bDisposeDone = true;
            }
        }
    }
}

【讨论】:

  • 赞成,很好的答案。虽然我认为滥用名称ComPtr&lt;T&gt; 有点过分。 (和匈牙利符号)
  • @RitchMelton 感谢您的投票。为什么你认为使用ComPtr&lt;T&gt; 是滥用名称?
猜你喜欢
  • 1970-01-01
  • 2011-02-15
  • 1970-01-01
相关资源
最近更新 更多