为什么要使用FinalReleaseComObject而不是ReleaseComObject?

36

我知道基本的区别是ReleaseComObject只是将某些计数器减一,而FinalReleaseComObject将其减为零。

所以通常听到的建议是调用FinalReleaseComObject,因为这样你就可以确信COM对象已经被释放了。

但是这让我想,这个计数器有什么作用呢?如果总是调用FinalReleaseComObject,那么不会破坏这个机制吗?如果在调用ReleaseComObject之前该计数器不等于1,那么可能有原因吗?

是什么导致它比应该为1的更高呢?

先谢谢了。

PS:我的COM经验仅限于使用Excel Interop。不确定这个问题是否局限于该领域(即在Office Interop之外,FinalReleaseComObject并不经常使用)。

更新1

Dan提到的文章讨论了在完成后使用ReleaseComObject的情况。根据我从文章中的理解,这是正常的方式。我认为如果你一直这样做,它应该能正常工作。在文章的评论中,作者建议某人调用ReleaseComObject循环,直到它真正被释放(文章是从2006年的,因此类似于调用FinalReleaseComObject)。但他还指出,这可能是危险的。

如果你希望在代码的特定位置调用RCW的Release()方法,可以使用一个循环来调用ReleaseComObject(),直到返回值为零为止。这样应该确保RCW将会调用Release()。但是,如果这样做,需要注意的是,当其他托管引用尝试使用该RCW时,将会导致异常。
这使我相信,一直调用FinalReleaseComObject并不是一个好主意,因为它可能会在其他地方引起异常。目前看来,只有当你绝对确定时才应该这样做。
然而,我在这方面缺乏经验。我不知道如何确保自己能够做到这一点。如果计数器在不应增加时增加,那么修复问题不是更好吗?如果是这样,那么我认为FinalReleaseComObject更像是一种hack而不是最佳实践。

1个回答

39

一个运行时可调用包装器(RCW)仅在封装的非托管COM接口上调用一次IUnknown.AddRef。然而,RCW还维护一个单独的计数器,以记录对RCW本身的托管引用数量。Marshal.ReleaseComObject调用会减少这个单独计数器的数量。当托管引用计数达到零时,RCW将在封装的非托管COM接口上调用一次IUnknown.Release。

Marshal.FinalReleaseComObject通过一次调用将托管引用计数变为零,因此立即调用封装的非托管IUnknown.Release方法(假设托管引用计数不为零)。

那么为什么需要同时使用Marshal.ReleaseComObject和Marshal.FinalReleaseComObject呢?当您希望表明您现在已经真正完成使用COM对象时,调用Marshal.FinalReleaseComObject可以避免编写循环,重复调用Marshal.ReleaseComObject直到它返回0。

为什么要使用Marshal.ReleaseComObject或Marshal.FinalReleaseComObject呢?我知道有两个原因:

第一个原因是确保尽快释放被封装的COM对象正在使用的非托管资源(例如文件句柄、内存等),因为结果调用了非托管的IUnknown.Release()方法。

第二个原因是确保调用非托管IUnknown.Release()方法的线程在您的控制下,而不是由终结器线程控制。

如果没有调用这两个Marshal方法,RCW的终结器将在RCW被垃圾收集后一段时间后 最终 调用非托管IUnknown.Release()方法。

有关详细信息,请参阅Visual C++团队博客文章Mixing deterministic and non-deterministic cleanup


3
从 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx 的 "Marshal.ReleaseComObject 方法" 文档中得知:为了确保运行时可调用包装器(runtime callable wrapper)和原始的 COM 对象都被释放,构建一个循环来调用此方法,直到返回的引用计数达到零。 - AMissico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接