【问题标题】:"COM object that has been separated from its underlying RCW cannot be used" with .NET 4.0.NET 4.0 中的“无法使用已与其基础 RCW 分离的 COM 对象”
【发布时间】:2012-04-23 23:38:16
【问题描述】:

我的 .NET 3.5 C# WinForms 应用程序中有一个类,它有五个方法。 每种方法都使用不同的 C++ COM 接口集。 我正在使用Marshal.FinalReleaseCOMObject 清理这些 COM 对象。此代码在此 .NET 平台上运行良好,没有任何问题。 但是,当我将此应用程序移至 .NET 4.0 时,我开始在其中一种方法中收到此错误,其中我将变量从 ICOMInterface1 转换为 ICOMInterface2,即:

ICOMInterface1  myVar= obj as ICOMInterface2; 

已经与其底层 RCW 分离的 COM 对象不能被 用过。

如果我删除使用Marshal.FinalReleaseCOMObject 的行,我不会收到此错误。

我在这里缺少什么?以及如何从 .NET 4.0 平台的内存中清理这些非托管 COM 对象?

【问题讨论】:

    标签: c# com com-callable-wrapper


    【解决方案1】:

    简单的答案是永远不要使用Marshal.FinalReleaseComObject,除非你绝对必须。如果你这样做了,你必须遵守一些额外的规则。

    在 .NET 中使用 COM 对象时,运行时会为该对象创建所谓的“RCW”或“运行时可调用包装器”。这个 RCW 只是一个普通的对象,它持有对象上的 COM 引用。当这个对象被垃圾回收时,它会在 COM 对象上调用 IUnknown::Release(),正如你所期望的那样。这意味着除非您的 COM 对象要求最后一个 Release() 在某个特定时刻完成,否则只需让垃圾收集器处理它即可。许多 COM 对象都属于这种情况,因此请务必确认您必须仔细管理对 Release() 的调用。

    因此,当您调用 FinalReleaseComObject 时,这实际上是减少 RCW 对 COM 对象的引用,直到它达到零,然后 RCW 释放 COM 对象。在这一点上,这个 RCW 现在是僵尸,任何使用它都会给出你所看到的异常。 CLR(默认情况下)只为任何底层 COM 对象创建一个 RCW,因此这意味着如果您使用的 COM API 返回相同的对象两次,它只会有一个 RCW。调用 FinalReleaseComObject 意味着突然所有对该 RCW 的使用都停止了。

    保证您拥有唯一的 Marshal.GetUniqueObjectForIUnknown 的唯一方法是防止任何 RCW 共享。但就像我之前所说的,在大多数 COM API 中,一开始就不需要这样做,所以不要这样做。

    Paul Harrington 写了一篇很好的 blog post 关于 [Final]ReleaseComObject 的文章,这是邪恶的。这是一种危险的武器,除非需要,否则只会伤害你。既然你在问这个问题,我怀疑你实际上根本不需要打电话。 :-)

    【讨论】:

    • 感谢 Jason 的输入。这个解释是否也适用于 ReleaseCOMObject 方法?因为,当我使用它而不是 FinalReleaseCOMObject 时,我仍然看到相同的行为......即它适用于 3.5但不是在 4.0 上。另外,想知道 .net 框架版本与这种行为有什么关系?是因为 GC 的工作方式现在在 4.0 中发生了变化吗?
    • 所以 RCW 本身有一个引用计数,当您调用 Marshal.ReleaseComObject 时该引用计数会减少。 FinalReleaseComObject 只是调用它,直到引用计数为零。
    • 至于 CLR 中的哪些变化导致了这种情况......如果没有调试器,很难说。
    • 杰森,我不清楚。 1、RCW是否会减少被包装对象(不是包装对象)的引用计数,直到它达到0?或者,它是否将包装器上的引用计数(如果有)直到它达到 0? 2、什么? CLR 为任何底层 COM 对象或 COM 对象创建单个包装器?复数?你的意思是,如果我有一些本机 COM 对象 IBuffer,并且我在该 IBuffer 上创建了两个包装器,我将有……呃,脑子坏了,1 个包装器和 2 个底层 IBuffers?这没有意义。
    • 这就是我想要的:每次我从 C# 创建一个 COM 对象时,我希望它为该 COM 对象创建一个 1-1 RCW。我们将 C++ 称为 IBuffer,将 C# 称为 IBuffer'(素数)。当我在 C# 中传递 IBuffer' 时,我假设它不会 addref 底层 IBuffer 对象。当我知道我永远不会再访问底层 IBuffer 时,我想调用一个方法 (Marshal.XXX) 来释放底层原生 *,但 c# 中的所有包装器现在都将持有一个无效或已处置的对象。这可能吗?
    猜你喜欢
    • 2021-09-23
    • 2011-03-11
    • 2011-04-26
    • 1970-01-01
    • 2011-11-21
    • 2012-04-27
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多