我也一直在研究这个问题,研究以 COM/.Net-Interop 为中心的应用程序,解决泄漏、挂起和崩溃问题。
简短回答:每次将 COM 对象从 COM 环境传递到 .NET。
长答案:
- 每个 COM 对象都有一个 RCW 对象 [测试 1] [参考 4]
- 每次从 COM 对象中请求对象时,引用计数都会增加(调用返回 COM 对象的 COM 对象的属性或方法,返回的 COM 对象引用计数将增加 1)[测试 1]
- 引用计数不会通过转换为对象的其他 COM 接口或移动 RCW 引用来增加 [测试 2]
- 每次在 COM 引发的事件中将对象作为参数传递时,引用计数都会增加 [参考 1]
附带说明:您应该始终在使用完 COM 对象后立即释放它们。将这项工作留给 GC 可能会导致泄漏、意外行为和事件死锁。如果您访问的对象不在创建对象的 STA 线程上,则这一点要重要十倍。[Ref 2] [Ref 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 引用计数规则!= COM 引用计数规则
2. Eran Sandler - Runtime Callable Wrapper Internals 和常见的陷阱
3. Eran Sandler - Marshal.ReleaseComObject 和 CPU Spinning
4. MSDN - 运行时可调用包装器