我有一个使用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
这样的东西。作者声称编译器创建单独的变量来保存将增加引用计数器的值。恕我直言,这是错误的,第一个例子证明了这一点。这是对的吗?
任何帮助都会很棒!
答案 0 :(得分:42)
我一直在研究这个问题,研究以COM / .Net-Interop为中心的应用程序,防止泄漏,挂起和崩溃。
简答:每次COM对象从COM环境传递到.NET。
答案很长:
旁注:您应该在完成使用后立即始终释放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
答案 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也会卸载,除非你让它对用户可见。