截至今天,使用COM对象的正确方法是什么?

时间:2016-06-19 06:13:15

标签: c# .net vb.net excel com

这是一个非常常见的问题,我决定问这个问题,因为这个问题在今天可能会有不同的答案。希望这些答案有助于理解使用COM对象的正确方法。 就个人而言,在对此主题发表不同意见后,我感到非常困惑。

过去5年,我曾经使用COM对象,规则对我来说非常清楚:

  1. 在代码行中使用单个句点。使用多个句点会在场景后面创建无法明确释放的临时对象。
  2. 不要使用foreach,而是使用for循环并在每次迭代时释放每个项目
  3. 不要调用FInalReleaseComObject,而是使用ReleaseComObject。
  4. 不要使用GC释放COM对象。 GC意图主要用于调试用法。
  5. 以与创建对象相反的顺序释放对象。
  6. 你们中的一些人在阅读完最后一行后可能会感到沮丧,这就是我所知道的如何正确创建/发布Com对象,我希望能得到更清晰无误的答案。

    以下是我在此主题中找到的一些链接。他们中的一些人告诉他们需要调用ReleaseComObject而其中一些不需要。

      

    “......在VSTO场景中,您通常不必使用ReleaseCOMObject ....”

      

    “......您应该使用此方法释放包含引用的基础COM对象......”

    更新

    此问题已被标记为过于宽泛。根据要求,我将尝试简化并提出更简单的问题。

    1. 使用COM对象或调用GC是否正确时,是否需要ReleaseComObject?
    2. VSTO方法是否会改变我们以前使用COM对象的方式?
    3. 我写的上述哪些规则是必需的,哪些是错的?还有其他人吗?

1 个答案:

答案 0 :(得分:16)

.NET / COM互操作设计精良,工作正常。特别是,.NET垃圾收集器正确跟踪COM引用,并且当它们没有剩余的运行时引用时将正确释放COM对象。通过调用Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...)来干扰COM对象的引用计数是一种危险但常见的反模式。不幸的是,一些不好的建议来自微软。

您的.NET代码可以正确地与COM交互,同时忽略所有5条规则。 如果确实需要触发不再从运行时引用的COM对象的确定性清理,则可以安全地强制GC(并可能等待终结器完成)。否则,您不必在代码中执行任何特殊操作,以处理COM对象。

有一个重要的警告,可能导致混淆垃圾收集器的作用。在调试.NET代码时,局部变量人为地将其生命周期延长到方法的末尾,以支持在调试器下观察变量。这意味着您可能仍然拥有对COM对象的托管引用(因此GC不会清理),而不仅仅是期望表单只是查看代码。此问题的一个很好的解决方法(仅在调试器下发生)是从GC清理调用中拆分COM调用的范围。

例如,这里有一些与Excel交互的C#代码,并且可以正常清理。您可以粘贴到控制台应用程序中(只需添加对Microsoft.Office.Interop.Excel的引用):

using System;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace TestCsCom
{
    class Program
    {
        static void Main(string[] args)
        {
            // NOTE: Don't call Excel objects in here... 
            //       Debugger would keep alive until end, preventing GC cleanup

            // Call a separate function that talks to Excel
            DoTheWork();

            // Now let the GC clean up (repeat, until no more)
            do
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            while (Marshal.AreComObjectsAvailableForCleanup());
        }

        static void DoTheWork()
        {
            Application app = new Application();
            Workbook book = app.Workbooks.Add();
            Worksheet worksheet = book.Worksheets["Sheet1"];
            app.Visible = true;
            for (int i = 1; i <= 10; i++) {
                worksheet.Cells.Range["A" + i].Value = "Hello";
            }
            book.Save();
            book.Close();
            app.Quit();

            // NOTE: No calls the Marshal.ReleaseComObject() are ever needed
        }
    }
}

您将看到Excel进程正确关闭,表明所有COM对象都已正确清理。

VSTO不会改变任何这些问题 - 它只是一个包装和扩展本机Office COM对象模型的.NET库。

关于此问题存在大量虚假信息和混淆,包括MSDN和StackOverflow上的许多帖子。

最终让我深入了解并找出正确建议的是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/以及在StackOverflow答案的调试器下发现引用保持活动的问题。

此一般指导的一个例外是COM对象模型要求以特定顺序释放接口。此处描述的GC方法无法控制GC释放COM对象的顺序。

我没有提及是否会违反COM合同。通常,我希望COM层次结构使用内部引用来确保正确管理对序列的任何依赖性。例如。在Excel的情况下,可以期望Range对象保持对父Worksheet对象的内部引用,以便对象模型的用户不需要显式保持两者都活着。

有些情况甚至Office应用程序对释放COM对象的顺序很敏感。一个案例似乎是使用OLE嵌入时 - 请参阅https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/04/11/excel-ole-embedding-errors-if-you-have-managed-add-in-sinking-application-events-in-excel-2/

因此,如果对象以错误的顺序发布,则可能会创建一个失败的COM对象模型,并且与这种COM模型的互操作将需要更多的关注,并且可能需要手动干涉引用。 / p>

但是对于与Office COM对象模型的一般互操作,我同意VS blog post调用&#34; Marshal.ReleaseComObject - 一个伪装成解决方案的问题&#34;。