c# - 截至目前,使用 COM 对象的正确方法是什么?

标签 c# .net vb.net excel com

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

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

  1. 在代码行中使用单个句点。使用多个句点会在幕后创建无法显式释放的临时对象。
  2. 不要使用 foreach,而是使用 for 循环并在每次迭代时释放每个项目
  3. 不要调用 FINalReleaseComObject,而是使用 ReleaseComObject。
  4. 不要使用 GC 来释放 COM 对象。 GC意图主要是为了调试使用。
  5. 按照对象创建的相反顺序释放对象。

读完最后几行后,你们中的一些人可能会感到沮丧,这就是我所知道的如何正确创建/释放 Com 对象,我希望得到的答案将使它更清晰且无争议。

以下是我找到的有关此主题的一些链接。其中一些告诉需要调用 ReleaseComObject,而另一些则不需要。

"... In VSTO scenarios, you typically don’t ever have to use ReleaseCOMObject. ..."

"...You should use this method to free the underlying COM object that holds references..."

更新:

此问题已被标记为过于宽泛。按照要求,我会尽量简化并提出更简单的问题。

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

最佳答案

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

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

有一个重要的警告,这可能会导致人们对垃圾收集器的作用感到困惑。调试 .NET 代码时,局部变量人为地将其生命周期延长到方法的末尾,以支持在调试器下监视变量。这意味着您可能仍然拥有对 COM 对象的托管引用(因此 GC 不会清理),晚于仅查看代码的预期形式。解决此问题(仅在调试器下发生)的一个好方法是将 COM 调用的范围与 GC 清理调用分开。

作为示例,下面是一些与 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 不会改变任何这些问题 - 它只是一个包装和扩展 native Office COM 对象模型的 .NET 库。

<小时/>

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

最终说服我仔细观察并找出正确建议的是这篇文章 https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/以及在 StackOverflow 答案中找到在调试器下保持事件的引用的问题。

<小时/>

此一般指导的一个异常(exception)是当 COM 对象模型要求以特定顺序释放接口(interface)时。此处描述的 GC 方法无法让您控制 GC 释放 COM 对象的顺序。

我没有任何引用资料来表明这是否会违反 COM 契约(Contract)。一般来说,我希望 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 模型的互操作需要更加小心,并且可能需要手动干扰引用。

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

关于c# - 截至目前,使用 COM 对象的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37904483/

相关文章:

vb.net - 从 .NET 应用程序中引用 JDK 命名空间是否需要安装 Java 运行时?

c# - WPF C# 圆形进度条 WindowsFormsHost

vb.net - 测试对象是否实现接口(interface)

c# - ManualResetEvent.WaitOne() 抛出 NullReferenceException : Object reference not set to an instance of an object

.net - Azure 上的 ASP.NET Core : Can not find assembly file dotnet-razor-tooling. exe

.net - WinForms事件生命周期

c# - 使用正则表达式从字符串中检索数字

c# - VB.NET和C#的语法比较系统、全面、完整

检测到 C# 无法访问的代码

c# - 您可以使用 LINQ to SQL 实体更新数据库吗?