时至今日,使用 COM 对象的正确方法是什么?
As of today, what is the right way to work with COM objects?
这是一个非常常见的问题,我决定提出这个问题,因为这个问题到今天可能会有不同的答案。希望这些答案将有助于理解什么是使用 COM 对象的正确方法。
就个人而言,在对这个问题发表不同意见后,我感到很困惑。
过去 5 年,我曾经使用 COM 对象,规则对我来说非常清楚:
- 在代码行中使用单个句点。使用多个句点会在幕后创建无法显式释放的临时对象。
- 不要使用 foreach,而是使用 for 循环并在每次迭代时释放每个项目
- 不要调用 FInalReleaseComObject,而是使用 ReleaseComObject。
- 不要使用 GC 来释放 COM 对象。 GC intent主要是调试使用
- 以创建对象的相反顺序释放对象。
有些人在阅读最后几行后可能会感到沮丧,这就是我所知道的如何正确 create/release 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 对象时是否需要 ReleaseComObject 或调用 GC 是正确的方法?
- VSTO 方法是否改变了我们过去使用 COM 对象的方式?
- 以上我写的规则,哪些是必须的,哪些是错误的?还有其他的吗?
.NET / COM 互操作设计精良,工作正常。特别是,.NET 垃圾收集器正确跟踪 COM 引用,并在 COM 对象没有剩余的运行时引用时正确释放它们。通过调用 Marshal.ReleaseComObject(...)
或 Marshal.FinalReleaseComObject(...)
来干扰 COM 对象的引用计数是一种危险但常见的反模式。不幸的是,微软提出了一些不好的建议。
您的 .NET 代码可以在忽略所有 5 条规则的情况下正确地与 COM 交互。
如果您确实需要触发不再从运行时引用的 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 不会改变任何这些问题 - 它只是一个包装和扩展本机 Office COM 对象模型的 .NET 库。
关于这个问题有很多虚假信息和混淆,包括 MSDN 和 Whosebug 上的许多 post。
最终说服我仔细研究并找出正确建议的是这个 post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ 以及在 Whosebug 答案中发现在调试器下保持活跃的引用问题。
此一般指南的一个例外是 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 模型的互操作需要更加小心,并且可能需要手动干预引用.
但对于与 Office COM 对象模型的一般互操作,我同意 VS blog post 调用 "Marshal.ReleaseComObject – a problem disguised as a solution"。
这是一个非常常见的问题,我决定提出这个问题,因为这个问题到今天可能会有不同的答案。希望这些答案将有助于理解什么是使用 COM 对象的正确方法。 就个人而言,在对这个问题发表不同意见后,我感到很困惑。
过去 5 年,我曾经使用 COM 对象,规则对我来说非常清楚:
- 在代码行中使用单个句点。使用多个句点会在幕后创建无法显式释放的临时对象。
- 不要使用 foreach,而是使用 for 循环并在每次迭代时释放每个项目
- 不要调用 FInalReleaseComObject,而是使用 ReleaseComObject。
- 不要使用 GC 来释放 COM 对象。 GC intent主要是调试使用
- 以创建对象的相反顺序释放对象。
有些人在阅读最后几行后可能会感到沮丧,这就是我所知道的如何正确 create/release 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 对象时是否需要 ReleaseComObject 或调用 GC 是正确的方法?
- VSTO 方法是否改变了我们过去使用 COM 对象的方式?
- 以上我写的规则,哪些是必须的,哪些是错误的?还有其他的吗?
.NET / COM 互操作设计精良,工作正常。特别是,.NET 垃圾收集器正确跟踪 COM 引用,并在 COM 对象没有剩余的运行时引用时正确释放它们。通过调用 Marshal.ReleaseComObject(...)
或 Marshal.FinalReleaseComObject(...)
来干扰 COM 对象的引用计数是一种危险但常见的反模式。不幸的是,微软提出了一些不好的建议。
您的 .NET 代码可以在忽略所有 5 条规则的情况下正确地与 COM 交互。 如果您确实需要触发不再从运行时引用的 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 不会改变任何这些问题 - 它只是一个包装和扩展本机 Office COM 对象模型的 .NET 库。
关于这个问题有很多虚假信息和混淆,包括 MSDN 和 Whosebug 上的许多 post。
最终说服我仔细研究并找出正确建议的是这个 post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ 以及在 Whosebug 答案中发现在调试器下保持活跃的引用问题。
此一般指南的一个例外是 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 模型的互操作需要更加小心,并且可能需要手动干预引用.
但对于与 Office COM 对象模型的一般互操作,我同意 VS blog post 调用 "Marshal.ReleaseComObject – a problem disguised as a solution"。