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

Nav*_*K N 20 c# com ms-office rcw office-pia

我有一个使用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);
    /* .... */
}
Run Code Online (Sandbox Code Playgroud)

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

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

任何帮助都会很棒!

Art*_*hur 45

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

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

答案很长:

  1. 对于每个COM对象,有一个RCW对象[测试1] [参考4]
  2. 每次从COM对象中请求对象时,引用计数都会递增(在返回COM对象的COM对象上调用属性或方法,返回的COM对象引用计数将增加1)[测试1]
  3. 通过强制转换为对象的其他COM接口或移动RCW参考[Test 2],不会增加引用计数
  4. 每次将对象作为COM引发的事件中的参数传递时,引用计数都会递增[参考1]

在一个侧面说明:您应该始终只要您使用完释放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
Run Code Online (Sandbox Code Playgroud)

测试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
Run Code Online (Sandbox Code Playgroud)

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

1. Johannes Passing的 - RCW参考计数规则!= COM参考计数规则

2. Eran Sandler - Runtime Callable Wrapper Internals和常见的陷阱

3. Eran Sandler - Marshal.ReleaseComObject和CPU Spinning

4. MSDN - 运行时可调用包装器