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

ehh*_*ehh 11 .net c# vb.net com excel

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

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

  1. 在代码行中使用单个句点.使用多个句点在场景后面创建无法明确释放的临时对象.
  2. 不要使用foreach,而是使用for循环,并在每次迭代时释放每个项目
  3. 不要调用FInalReleaseComObject,而是使用ReleaseComObject.
  4. 不要使用GC释放COM对象.GC意图主要用于调试用法.
  5. 以与创建对象相反的顺序释放对象.

你们中的一些人在阅读完最后一行后可能会感到沮丧,这就是我所知道的如何正确创建/发布Com对象,我希望能得到更清晰无误的答案.

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

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

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

更新:

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

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

Gov*_*ert 19

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

您的.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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您将看到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-下沉应用事件合的Excel-2 /

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

但是对于与Office COM对象模型的一般互操作,我同意VS博客文章称为"Marshal.ReleaseComObject - 一个伪装成解决方案的问题".

  • 我想补充一点,有许多COM对象用非托管代码编写,确实有bug(不是谈论微软的大的一般都可以).这些错误导致P/Invoke被打破的错误印象,因为它们有时迫使.NET开发人员应用COM恐慌巫术魔法(在任何地方添加ReleaseComObject,以相反的顺序释放对象等) (2认同)
  • 我知道这已经很老了,但我很好奇这些建议中有多少适用于 COM 互操作代码,例如(不是?)清理托管 .net 代码的 VBIDE 加载项中的可停靠工具窗口通过 COM 应用程序,而不是托管 .net 代码*创建和销毁*一堆 COM 对象。 (2认同)