RCW&在C#中使用COM互操作时的引用计数

时间:2011-01-04 08:15:35

标签: c# com ms-office office-pia rcw

我有一个使用Office互操作程序集的应用程序。我知道运行时管理的“运行时可调用包装器(RCW)”。但我不太确定引用计数如何增加。 MSDN说,

  

RCW只保留一个引用   包装COM对象无论如何   调用它的受管客户端数量。

如果我理解正确,请参阅以下示例

using Microsoft.Office.Interop.Word;

static void Foo(Application wrd)
{
    /* .... */
}

static void Main(string[] args)
{
    var wrd = new Application();
    Foo(wrd);
    /* .... */
}

我将实例wrd传递给另一个方法。但这不会增加内部引用计数。所以我想知道引用计数增加的场景是什么?任何人都可以指出引用计数增加的情况吗?

另外,我读了一些博客,说明在使用COM对象编程时避免使用双点。像wrd.ActiveDocument.ActiveWindow这样的东西。作者声称编译器创建单独的变量来保存将增加引用计数器的值。恕我直言,这是错误的,第一个例子证明了这一点。这是对的吗?

任何帮助都会很棒!

5 个答案:

答案 0 :(得分:42)

我一直在研究这个问题,研究以COM / .Net-Interop为中心的应用程序,防止泄漏,挂起和崩溃。

简答:每次COM对象从COM环境传递到.NET。

答案很长:

  1. 对于每个COM对象,有一个RCW对象[测试1] [参考4]
  2. 每次从COM对象中请求对象时,引用计数都会递增(在返回COM对象的COM对象上调用属性或方法,返回的COM对象引用计数将增加1)[测试1]
  3. 引用计数不会通过强制转换为对象的其他COM接口或移动RCW参考[Test 2]
  4. 来增加
  5. 每次将对象作为COM引发的事件中的参数传递时,引用计数会递增[参考1]
  6. 旁注:您应该在完成使用后立即始终释放COM对象。将此工作留给GC可能会导致泄漏,意外行为和事件死锁。如果您访问的对象不是在创建的STA线程上,那么这十分重要。 [参考2] [参考3] [痛苦的个人经历]

    我希望我已涵盖所有案例,但COM是一个艰难的cookie。 欢呼声。

    测试1 - 参考计数

    private void Test1( _Application outlookApp )
    {
        var explorer1 = outlookApp.ActiveExplorer();
        var count1 = Marshal.ReleaseComObject(explorer1);
        MessageBox.Show("Count 1:" + count1);
    
        var explorer2 = outlookApp.ActiveExplorer();
        var explorer3 = outlookApp.ActiveExplorer();
        var explorer4 = outlookApp.ActiveExplorer();
    
        var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer4);
        var count2 = Marshal.ReleaseComObject(explorer4);
        MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals);
    }
    Output:
    Count 1: 4
    Count 2: 6, Equals: True
    

    测试2 - 参考计数续

    private static void Test2(_Application outlookApp)
    {
        var explorer1 = outlookApp.ActiveExplorer();
        var count1 = Marshal.ReleaseComObject(explorer1);
        MessageBox.Show("Count 1:" + count1);
    
        var explorer2 = outlookApp.ActiveExplorer();
    
        var explorer3 = explorer2 as _Explorer;
        var explorer4 = (ExplorerEvents_10_Event)explorer2;
        var explorerObject = (object)explorer2;
        var explorer5 = (Explorer)explorerObject;
    
        var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer5);
        var count2 = Marshal.ReleaseComObject(explorer4);
        MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals);
    }
    Output:
    Count 1: 4
    Count 2: 4, Equals: True
    

    除了我的经验和测试之外,我继续传播的来源:

    1. Johannes Passing's - RCW Reference Counting Rules != COM Reference Counting Rules

    2. Eran Sandler - Runtime Callable Wrapper Internals and common pitfalls

    3. Eran Sandler - Marshal.ReleaseComObject and CPU Spinning

    4. MSDN - Runtime Callable Wrapper

答案 1 :(得分:3)

我还没有看到RCW的代码 - 甚至不确定它是否是SSCLI的一部分 - 但是我必须在SlimDX中实现一个跟踪COM对象生命周期的类似系统,并且必须对它进行一些研究。 RCW。这就是我记得的,希望它是相当准确的,但要带上一点点盐。

当系统第一次看到COM接口指针时,它只是进入缓存以查看该接口指针是否有RCW。据推测,缓存将使用弱引用,以免阻止RCW的最终化和收集。

如果该指针有一个实时包装器,系统将返回包装器 - 如果以增加接口引用计数的方式获取接口,则可能RCW系统此时将调用Release()。它找到了一个实时包装器,因此它知道包装器是一个单独的引用,并且它想要保持一个引用。如果缓存中没有实时包装器,它会创建一个新包装并返回它。

包装器从终结器调用底层COM接口指针上的Release。

包装器位于您和COM对象之间,并处理所有参数编组。这也允许它允许它获取任何接口方法的原始结果,该接口方法本身是另一个接口指针,并在返回包装的接口指针之前通过RCW高速缓存系统运行该指针以查看它是否存在。

不幸的是,我不太了解RCW系统如何处理代理对象生成以跨应用程序域或线程公寓发送内容;它不是我需要为SlimDX复制的系统的一个方面。

答案 2 :(得分:1)

您不需要任何特殊待遇。运行时只保留对COM对象的一个​​引用。原因是GC跟踪所有托管引用,因此当RCW超出范围并被收集时,将释放COM引用。传递托管引用时,GC会为您跟踪它 - 这是基于GC的运行时相对于旧的AddRef / Release方案的最大优势之一。

除非您想要更确定的释放,否则无需手动调用Marshal.ReleaseComObject。

答案 3 :(得分:0)

accepted solution有效,但此处还有一些其他背景信息。

RCW在内部为其COM对象包含一个或多个本机COM对象接口引用。

当RCW释放其底层COM对象时,由于收集了垃圾或由于Marshal.ReleaseComObject()被调用,它会释放所有内部保存的COM对象接口。

这里实际上有许多引用计数 - 一个确定.NET的RCW何时应该释放它的底层COM对象接口,然后每个原始COM接口都有自己的引用计数,就像在常规COM中一样。

此处获取原始COM IUnknown接口引用计数的代码:

int getIUnknownReferenceCount(object comobject)
{
    var iUnknown = Marshal.GetIUnknownForObject(comObject);
    return Marshal.Release(iUnknown);
}

您可以使用Marshal.GetComInterfaceForObject()为对象的其他COM接口获得相同的内容。

除了accepted solution中列出的方法之外,我们还可以通过调用类似Marshal.GetObjectForIUnknown()的内容来人为地增加.NET RCW引用计数。

这里的示例代码利用该技术获取给定COM对象的RCW引用计数:

int comObjectReferenceCount(object comObject)
{
    var iUnknown = Marshal.GetIUnknownForObject(comObject);
    Marshal.GetObjectForIUnknown(iUnknown);
    Marshal.Release(iUnknown);
    return Marshal.ReleaseComObject(comObject);
}

答案 4 :(得分:-2)

您需要在wrd变量上调用Marshal.ReleaseComObject以释放对应用程序一词的引用。

这样一来,如果Word不可见并且你关闭了你的应用程序,那么exe也会卸载,除非你让它对用户可见。