.NET和COM互操作性:从.NET客户端释放COM

7

假设我有一个COM对象(非托管)和.NET客户端。

在释放COM对象时,是否需要从.NET客户端调用Marshal.FinalReleaseComObject方法?


3
http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx - Hans Passant
2个回答

2
不需要在.NET客户端中显式释放COM对象。COM对象将像任何其他.NET对象一样被收集,并在所有对其的引用被删除后释放其底层本机句柄。
明确使用FinalReleaseComObject实际上可能会导致编程错误。如果代码中的另一个组件仍在引用COM对象,则您将从其下面拔出本机对象。这可能会导致运行时故障。

在理论上可能没有必要,但在实践中绝对需要。此外,不要使用FinalReleaseComObject,而是使用ReleaseComObject来释放每个COM对象,当它们被传递到COM-.NET边界时。我做Outlook Addin编程:我可以说,给对象明确的生命周期是创建工作Add-in的唯一方法。 (我使用一个支持Dispose的帮助器/包装类,我用using语句调用它。) - user166390
3
错误的做法。如果必须这样做,可以使用GC.Collect() + GC.WaitForPendingFinalizers(),它永远不会出错。 - Hans Passant
1
@HansPassant,这在Outlook对象模型中不起作用。各种相关的“解决方案”都涉及尝试连续两次调用该序列。在实践中(我处理的情况下)它根本行不通。 - user166390
1
在编程中,如果GC.Collect + GC.WaitForPendingFinalizers不起作用,但FinalReleaseComObject起作用,那么很有可能你的程序中的某个地方,FinalReleaseComObject未释放对COM对象的托管引用。 - JaredPar
1
只有一种COM,它对于Outlook并没有不同。也许你应该问一个相关的问题。 - Hans Passant
1
@HansPassant 做了很多月了。你所描述的方式并不可靠。这与GC pass没有(必要地)挑选出需要被回收的RCWs有关,如果我没记错的话。这真的和using vs.希望Stream finalizer运行没有什么区别。有时它只是在应该运行时没有运行。再加上缓存(在OOM中臭名昭著),这只会“使事情不能正常工作”。 - user166390

2

一个经过修改的"是"和一个"否"。

首先,要记住 ReleaseComObject 并不会自动减少真实的COM对象引用计数。相反,它会减少内部RCW计数器。(每次将相同的COM对象从COM->NET移动时,它将使用相同的RCW并将RCW计数器增加一次。)同样,FinalRelaseComObject 也会影响RCW计数器,并有效地将其设置为0。当RCW计数器变为零时,.NET将减少实际的COM引用计数。

所以,"是",但是根据以下规则进行修改:

  1. 每次一个对象从COM->NET越界时,应该对其调用 ReleaseComObject ,但不应调用 FinalReleaseComObject。也就是说,每次对象越界时都应该被调用一次,即使它导致引用等于RCW。
  2. RCW(只是代理包装器)的引用数量无关紧要;只有对象越界的次数才有关系。请参见规则#1。 (控制引用及其保留者很重要,但在这种情况下,“最糟糕”的情况是使用“已分离的RCW”引发异常,这与使用Disposed流没有区别。)

我提到了上述规则,而不是 FinalReleaseComObject ,因为RCW对象被缓存在标识代理中,所以 使用 FinalReleaseComObject 将会影响您甚至不知道的COM->NET边界越界!(这很糟糕,在这一点上,我完全同意JaredPars的答案。)

而且 "否" 是指当一个RCW被回收时(调用终结器),它将自动“释放”COM引用(实际上在自身上调用FinalReleaseComObject)。请记住,由于只有一个RCW对象,这意味着只要.NET中存在对它的引用,它就永远不会被回收。如果RCW被回收,则没有问题,因为根据上述情况,没有对该RCW的更多引用,因此.NET知道可以将COM引用计数减少一次(可能会导致销毁COM对象)。

然而,由于.NET GC很挑剔且不确定性强,依赖此行为意味着对象生命周期无法控制。这可能会在使用Outlook对象模型时引起许多微妙的问题。(与其他COM服务器相比,可能不容易出现问题,但OOM在内部进行有趣的“缓存”对象。如果不显式控制生命周期,很容易出现“项目已被修改”的异常。)
在我的代码中,我有一个支持IDisposable的ComWrapper类。这使我能够传递通过COM->NET获取的RCW对象,并拥有清晰的生命周期所有权。在切换到这种方法后,我的Outlook-Addin开发几乎没有出现任何问题(实际上几乎没有),之前则遇到了许多问题。 “缺点”是需要确定COM->NET边界,但一旦确定,所有生命周期都将被正确处理。
愉快的编码。

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