这是一个非常常见的问题,我决定提出这个问题,因为到今天为止这个问题可能会有不同的答案。希望这些答案将有助于理解使用 COM 对象的正确方法。 就我个人而言,在这个问题上得到不同的意见后,我感到非常困惑。
过去 5 年,我曾经使用 COM 对象,规则对我来说非常清楚:
- 在代码行中使用单个句点。使用多个句点会在幕后创建无法显式释放的临时对象。
- 不要使用 foreach,而是使用 for 循环并在每次迭代时释放每个项目
- 不要调用 FINalReleaseComObject,而是使用 ReleaseComObject。
- 不要使用 GC 来释放 COM 对象。 GC意图主要是为了调试使用。
- 按照对象创建的相反顺序释放对象。
读完最后几行后,你们中的一些人可能会感到沮丧,这就是我所知道的如何正确创建/释放 Com 对象,我希望得到的答案将使它更清晰且无争议。
以下是我找到的有关此主题的一些链接。其中一些告诉需要调用 ReleaseComObject,而另一些则不需要。
- How to properly release Excel COM objects (2013 年 11 月)
- Proper Way of Releasing COM Objects in .NET (2011年8月)
- Marshal.ReleaseComObject Considered Dangerous (2010年3月)
- ReleaseCOMObject (2004 年 4 月)
"... In VSTO scenarios, you typically don’t ever have to use ReleaseCOMObject. ..."
- MSDN - Marshal.ReleaseComObject Method (当前 .NET Framework 版本):
"...You should use this method to free the underlying COM object that holds references..."
更新:
此问题已被标记为过于宽泛。按照要求,我会尽量简化并提出更简单的问题。
- 使用 COM 对象或调用 GC 是正确的方法时,是否需要 ReleaseComObject?
- VSTO 方法是否改变了我们使用 COM 对象的方式?
- 我写的上述规则哪些是必需的,哪些是错误的?还有其他人吗?
最佳答案
.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/