Office开发必备知识----为什么要释放非托管Com资源

https://www.cnblogs.com/Charltsing/p/RealeaseComObject.html

QQ:564955427

目前,国内Office插件开发的风头正盛,很多VBAer都纷纷加入到vb.net或者C#等托管语言的插件开发大军中,但是大部分人从vba转到托管语言的时候,都没有从理论上学习一下托管语言的特性,直接使用vba代码暴力翻译成托管语言,简单粗暴地在代码中使用,只要代码不报错就认为程序没问题了。

然而,直接在代码中暴力使用Com对象会在托管对象中造成释放问题,引发内存泄漏,严重时可能会导致Excel等宿主程序报错,为了解决这个问题,我下面简单介绍一下在托管代码中使用非托管Com资源的释放问题。

1、什么是Com对象
Com是微软提出来的在组件程序之间进行交互的标准,以Excel为例:application,workbook,workbooks,sheet,sheets,range等等都是Com对象。

2、为什么要释放Com对象
Office程序是非托管语言编写的,C#之类的托管语言要去操作非托管语言编写的程序资源就要解决数据交换的格式和结构问题,微软采用Interop(互操作程序集)来解决这个问题。当托管语言访问非托管语言组件的时候一般通过Com方式进行,CLR会给每个COM对象按每进程生成一个RCW(Runtime Callable Wrappers运行时可调用包装器),并用计数器记录Com对象被引用的次数,每引用一次,计数器加1;每释放一次,计数器减1。这种RCW包装会带来额外的资源开销,当计数器为0的时候,RCW资源才会被释放。所以,如果不释放Com对象,RCW(与此类似的还有CCW,COM Callable Wrapper)会造成相关内存始终被占用,即使Com对象消失,内存也不会被释放,这就是所谓的内存泄漏。

3、不释放Com资源会有什么问题
内存泄漏会导致程序可用资源被耗尽,进而产生不可预料的问题。除此之外,由于RCW的权限高于应用程序,所以只有当所有的Com资源被释放之后,应用程序的进程才能退出。这就是为什么有些人的程序在执行完毕退出之后,后台还会残留Office进程的原因。

4、我没有在代码中释放Com资源貌似程序运行得也挺好,而且msdn在VSTO开发中也没有强调这一点,为什么?
如果代码书写得当的话,GC会在后台处理Com的释放问题,同时处理RCW资源。而且从Office2007开始,应用程序在退出的时候,会处理一些泄漏的对象。所以在微软MSDN中,只是要求不要丢失对Com资源的引用即可。只要不在代码中使用隐含的Com对象引用,GC是可以处理大部分Com资源释放问题的。

5、既然GC可以解决大部分Com资源释放问题,为什么还要谈这个释放问题呢?
因为GC有些时候不靠谱。依赖GC做资源释放的最大问题是你无法控制资源的释放时机,当你处理了成千上万的单元格、工作表和工作簿之后(注意同一个工作簿只有一个RCW,而同一个Range因为引用的不同可能会有无数个RCW),你不知道是不是还有足够的内存给你做下一步操作,这会给商业程序开发带来不可预计的风险。此外,Com对象的事件订阅和取消订阅也是个大问题,GC是无法100%正确处理此类问题的,这有可能会造成严重的内存泄漏甚至宿主报错崩溃。此外,根据开发人员能力的不同,还可能会发生更严重的循环引用问题,造成Com对象的死锁,导致RCW无法释放。

6、如何手工进行非托管Com资源的释放?
参见下面的代码

注意代码的书写要慎重,不得使用会丢失引用的Com对象,例如下面的代码是一个错误示例

上面的示例只是演示了最简单情况下COM资源的释放,实际应用中,由于GC的存在以及宿主程序对泄漏内存的处理,上面的ReleaseComObject语句也可以不需要执行。但是,不执行ReleaseComObject的前提是,开发人员应该按照规范书写代码,不要丢失对Com资源的引用,更不能造成Com资源的循环引用以及由于事件订阅导致的内存泄漏。同时还要处理好try-catch-finally的问题。

7、手工进行非托管Com资源的释放的坏处是什么?
很明显,你的代码会因显式存储和释放COM代理对象而变得臃肿和不可读。此外,各种事件中Com参数的传递也会造成复杂的RCW计数问题,这可能会导致无法正确的手工释放资源。
此外,滥用Marshal.ReleaseComObject,特别是Marshal.FinalReleaseComObject,可能会造成RCW为零断开Com对象资源,导致同时运行的其他程序或插件无法继续使用某些Com对象。因此手工释放Com资源对开发人员的素质要求较高。

顺便再补充一点,微软并不赞成在代码中显式地调用ReleaseComObject,这里牵涉到很复杂的原因。
如果你明白其中的深层次问题,不想使用ReleaseComObject的话,也许在Com对象使用完毕之后,调用两次GC来释放它也是个省事的办法。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

因为从Office2007开始,应用程序会在退出的时候处理泄漏的对象引用,所以,在很多时候,你不会发现内存泄漏之后有什么问题,因此采用上面的方式也可以凑合解决泄漏问题。当然前提依然是开发人员应该按照规范书写代码,确保每个Com对象只有一个引用,否则,单靠GC还是无法真正释放Com引用的。

当然,上述办法并不是万能的良药,在调用GC之前,你可能还需要处理一下其它的问题,以后再慢慢细述。

8、关于VSTO是否需要调用ReleaseCOMObject的问题,答案如下:
原则上你不需要在VSTO中使用ReleaseCOMObject。VSTO为每一个VSTO Add-Ins创建了一个AppDomain,当卸载VSTO Addin的时候,AppDomian也会被卸载,CLR会卸载与其相关的所有资源。但是特殊情况例外。
例如:你在一个Word中操作了PowerPoint的资源(相当于PIA操作Office),后者可能需要开发者正确使用ReleaseCOMObject来确保application能够正常关闭。

同样,需要注意的是,VSTO并不能及时地释放COM占用的资源,这可能会导致大量的内存占用,这一点和第五条谈到的GC的原因是一致的。

9、有没有其它的方案?
你可以有以下几个选择:
a、不使用pia或vsto,改用第三方控件,例如npoi之类的。
b、使用SafeComWarpper包装器之类的开源项目,为每个Com对象建立一个Dispose(可能会影响效率)。
c、微软推荐过shim和Appdomain,后者是VSTO的方案。
d、通过ExcelDna调用C API也是个可以考虑的选择。

10、总结
a.对于一般的Office插件开发来说,如果你在使用VSTO开发Office插件,那么大部分时候不需要在代码中释放Com资源,AppDomain会很好地解决这个问题。除非你需要及时释放内存空间,或者调用了另一个Com宿主(这相当于PIA操作Office)。
b.对于普通的Com应用,例如通过PIA操作Office,一定要慎重地考虑Com资源释放问题。是否使用ReleaseComObject取决于开发者的技术能力。如有可能,建议使用现成的一些SafeComWarpper包装器开源项目来最大程度上解决Com资源自动释放问题。
c.如果出于资源占用方面的考虑,或者你的程序需要支持2003或更早期的版本,你只能手工释放Com资源,请在代码开发中认真测试Com资源的RCW计数变化情况,然后慎重的使用ReleaseComObject,并且在不了解你在做什么的情况下绝不要使用FinalReleaseComObject。适当地调用GC,可以很好地帮助释放资源。

 

一些参考资料

https://www.add-in-express.com/creating-addins-blog/2011/11/04/why-doesnt-excel-quit/
https://www.add-in-express.com/creating-addins-blog/2008/10/30/releasing-office-objects-net/
https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/

http://www.it1352.com/533884.html 我何时应使用 Marshal.FinalReleaseComObject 与 Marshal.ReleaseComObject ?
https://blogs.msdn.microsoft.com/yvesdolc/2004/04/17/discussion-of-marshal-releasecomobject-and-its-dangers/
http://jake.ginnivan.net/vsto-com-interop/

https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2003/aa679806(v%3doffice.11)
https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2003/aa679807(v%3doffice.11)
https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2003/aa679808(v%3doffice.11)

posted @ 2018-08-26 13:49  Charltsing  阅读(902)  评论(0编辑  收藏  举报