【问题标题】:How to release COM objects in Silverlight 4如何在 Silverlight 4 中释放 COM 对象
【发布时间】:2011-08-02 01:53:50
【问题描述】:

在将 COM Interop 与 Office(通常是 Excel)一起使用时,我始终小心确保在每次引用时都调用 Marshal.ReleaseComObject,以避免 Excel 不退出 as described in this KB article 的问题。

当我从 OOB Silverlight 应用程序(使用 AutomationFactory.CreateObject)使用 Interop 时,如何确保 Excel 退出?

Silverlight 没有 Marshal.ReleaseComObject 方法,甚至调用 GC.CollectGC.WaitForPendingFinalizers 也无济于事。

在没有释放 COM 引用的机制的情况下,微软肯定没有将此功能添加到 Silverlight 吗?在我看来,这似乎是自动化进程外 COM 服务器(如 Excel)的最佳选择。

一个令人惊讶的遗漏,更何况 Pete Brown 在他的书“Silverlight 4 in Action”的第 5.5 节中谈到AutomationFactory.CreateObject,即:

此功能的主要目的是允许其他应用程序自动化,包括 Microsoft Office。

更新以回应 Hans 的 cmets。

我不相信 Office 应用程序的典型自动化中存在“无声刺客”问题。一个常见的用法可能类似于以下内容,我在 WinForms 应用程序中反复使用它,但从未遇到 Hans 链接的文章中描述的“中毒 RCW”:

  • 创建一个 Excel.Application 实例
  • 打开或创建工作簿
  • 将数据写入工作簿
  • 如果一切顺利,则显示 Excel,关闭工作簿并调用 Application.Quit,否则。
  • 调用 Marshal.ReleaseComObject 释放所有 Excel 对象引用。

未能按照 Hans 的建议调用 Marshal.ReleaseComObject 将导致 Excel.exe 的多个副本运行,如上面提到的知识库文章中所述 - 非常不受欢迎。

更新 2

我用来复现的示例是来自 Pete Brown 的书 Silverlight 4 in action 的源代码示例,此页面上有下载链接。示例解决方案 AutomatingExcel 位于 Ch05.zip / 5.03 中。复制:

  • 确保没有 Excel 实例正在运行
  • 运行 AutomatingExcel 示例
  • Excel 工作簿已打开
  • 关闭 Excel
  • 使用任务管理器观察 Excel 仍在运行。

将所有动态变量设置为 null 并调用 GC.Collect() 似乎可以正常工作,正如 AnthonyWJones 的回答中所指出的那样。

更新 2

Otaku 的答案是我一直在寻找的——通过将引用包装在 using 语句中,COM 引用被释放而无需调用 GC.Collect。一些实验表明,与上面引用的知识库文章中描述的标准Marshal.ReleaseComObject 解决方案不同,它更能容忍未能处理每个引用。

如果对必须处理的内容进行权威处理以确保释放所有 Excel 引用,将会很有趣。

【问题讨论】:

  • @Hans:Silverlight 在Marshal 类中没有这些方法。
  • @Anthony - 它解释了为什么这些方法都不是一个好主意。 “沉默的刺客”链接非常相​​关。
  • @Hans - “它解释了为什么这些方法都不是一个好主意” - 你能提出一个替代方案吗?请参阅问题更新。这也不是您链接的问题的副本。
  • 找到真正的问题,为什么终结器线程不再运行了?通常的原因是“头灯中的鹿”的原因,您在完成互操作代码的调试后让您的程序停止做任何有趣的事情。

标签: silverlight com interop


【解决方案1】:

您可以实现IDisposable 接口。我见过的最好的例子是http://csfun.blog49.fc2.com/blog-entry-79.html。博客条目是日文的,但如果您不懂日文,请在 Chrome 中打开并让 Google 为您翻译页面。

另外,如果您只想直接获取 COM 包装器的源代码示例,您可以下载它附带的示例应用程序:SilverOffice

【讨论】:

  • +1 谢谢 - 这是我一直在寻找的答案 - 如何在不调用 GC.Collect 的情况下释放 COM 对象。使用 dynamic 关键字创建的引用似乎实现了 IDisposable,并且这释放了 COM 引用。可惜 MSDN 没有提供关于这个主题的更明确的信息:msdn.microsoft.com/en-us/library/dd264733.aspx
  • 这很有趣,我没有想过要测试和使用IDisposable,不幸的是,disposing 不会释放 COM 对象。
  • @Joe:我刚刚通过将 Dispose 添加到我的代码中进行了测试。如果没有 GC.Collect,我仍然会得到多个 Excel 实例。
  • @AnthonyWJones:您是否尝试过引用的示例项目 SilverOffice?该代码有一个InstanceManager 类,可确保只使用(和处置)一个 Excel 实例。
  • 不,我没有,我只是想看看 IDisposable used raw 是否解决了这个问题。当然,使用实例管理器以及大量其他包装工作对于广泛的办公室工作非常有意义。然而,潜在的问题似乎仍然存在;无论是否调用 Dispose,COM 对象似乎只在最终确定时才真正被释放。
【解决方案2】:

看看这段代码:-

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        dynamic app = AutomationFactory.CreateObject("Excel.Application");
        dynamic book = app.Workbooks.Add();
        dynamic sheet = app.ActiveSheet();

        sheet = null;
        book.Close(false);
        book = null;
        app.Quit();
        app = null;

        GC.Collect();
    }

Excel 进程出现然后消失。删除GC,Excel 过程将继续。如果您逐字复制此代码,您会得到相同的结果吗?如果是这样,那么它会建议在您的代码中的某处对 excel 对象的引用仍然可以从线程堆栈或静态字段之一访问。

您是否曾经在字段中保存过 excel 对象(而不是局部变量)?

您是否持有看似可变但从用作事件处理程序的动态委托或 lambda 引用的 excel 对象?

您是否将事件处理程序附加到寿命短的对象中的长寿命对象?如果是这样,您是否确保正确地与这些处理程序分离?

其中许多事情可以让开发人员将他们认为是准备好的对象留给 GC,但 GC 发现它们是可访问的,因此不是回收的候选对象。

如果上面的代码表现不一样,那么我们完全在寻找另一个问题。我正在从最新的 SL 4 运行时在 Server 2008 R2 上使用 Office 2007。但是,如果我们因为设置而有变化,那么我们就处于非常不稳定的状态。

编辑

经过一些测试,这似乎是有效的:-

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        using (dynamic app = AutomationFactory.CreateObject("Excel.Application"))
        {
            using (dynamic book = app.Workbooks.Add())
            {
                using (dynamic sheet = app.ActiveSheet())
                {

                }
                book.Close();
            }
            app.Quit();
        };

        GC.Collect();
    }

但是,如果不使用 GC,最终会导致不需要的 Excel 进程继续运行。

【讨论】:

  • 看起来很有希望。我的目标环境是 Excel 2003,但我刚刚在家里尝试了 Excel 2007 上的代码,它可以工作。似乎需要将变量设置为 null ,不知道为什么,因为它们无论如何都应该无法访问。尽管如此,GC.Collect() 看起来确实像重炮,只是为了释放 COM 引用。
  • @Joe:不一定,如果您从我的代码中删除 x= null,那么在调用 GC.Collect 时,您可以看到尽管调用了 GC.Collect,但 Excel 进程仍在继续。这是因为当调用 Collect 时,所有线程都被挂起,GC 将检查它们的堆栈,在那里它可能会找到局部变量持有的引用。我使用“可能”这个词,因为优化可能会使它们无法访问。如果您将大部分代码移到另一个函数中并从 Click 事件中调用它,则不需要在分配给 null 之后调用 GC.Collect。
  • +1 感谢您的持续调查。您的代码可能具有隐式实例化(“双点”问题)。您是否尝试过“using(dynamic workbooks = app.Workbooks)”,然后是“workbooks.Add()”,以确保所有引用都被显式实例化和处置。对于标准 COM 互操作,必须小心执行此操作,如果不这样做可能会解释为什么您最终会运行进程。
  • @Joe:不错!是的,确保Workbooks 返回的对象也被处理确实可以解决问题。不需要 GC.Collect。
【解决方案3】:

我会考虑在 WCF 服务中构建 Excel 文件。你可以在那里做所有的清理工作。将字节发送到您的 Silverlight 应用程序并使用 SaveFileDialog 将它们发送给用户。

Silverlight 对客户端文件系统的访问有限制。在客户端系统上操作 Excel 文件,甚至假设客户端安装了 Excel 似乎违反了这一点。

【讨论】:

  • 虽然您的方法有效,但它并不能回答问题。并且可能有正当理由希望在提升信任的 OOB 应用程序的客户端自动化 Excel。
【解决方案4】:

通过在 using 语句中包装引用来释放 COM 引用

请注意:using 语句只是 try/catch/finally 在 finally 子句中使用 Dispose() 的语法糖。

此外,大多数应用程序不允许在这种情况下使用语句,因为 COM 对象的创建和清理分布在不同的位置/方法中。

这里需要的关键行是:

((IDisposable)_excel).Dispose();  // Release COM Interface

假设:

dynamic _excel = AutomationFactory.CreateObject("Excel.Application");

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-25
    • 2012-09-17
    • 2012-03-05
    • 2014-02-14
    • 2015-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多