我也一直在研究这个问题,正在开发一个基于COM/.Net-Interop的应用程序,解决泄漏、挂起和崩溃的问题。
简短回答:每次将COM对象从COM环境传递到.NET时。
长回答:
- 每个COM对象都有一个RCW对象 [Test 1] [Ref 4]
- 引用计数每次从COM对象内部请求该对象时会增加(对COM对象调用返回COM对象的属性或方法,返回的COM对象引用计数将增加一个)[Test 1]
- 通过转换为对象的其他COM接口或移动RCW引用不会增加引用计数 [Test 2]
- 在由COM引发的事件参数中传递对象时,引用计数会增加
另外:您应该始终在使用完COM对象后立即释放它们。将此工作留给GC可能会导致泄漏、意外行为甚至死锁。如果您访问在其创建时不在STA线程上的对象,这十倍重要。[Ref 2] [Ref 3] [令人痛苦的个人经历]
希望我已涵盖所有情况,但是COM确实很棘手。
Test 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的-RCW引用计数规则!= COM引用计数规则
2. Eran Sandler - Runtime Callable Wrapper内部和常见陷阱
3. Eran Sandler - Marshal.ReleaseComObject 和CPU自旋
4. MSDN - Runtime Callable Wrapper