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开始,应用程序会在退出的时候处理泄漏的对象引用,所以,在很多时候,你不会发现内存泄漏之后有什么问题,因此采用上面的方式也可以凑合解决泄漏问题。当然前提依然是开发人员应该按照规范书写代码。

当然,上述办法并不是万能的良药,在调用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)

Office开发必备知识----为什么要释放非托管Com资源的更多相关文章

  1. 5.C#释放非托管资源1

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  2. C#编程(七十四)----------释放非托管资源

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  3. 移动web开发(一)——移动web开发必备知识

    参考: 移动终端开发必备知识.http://isux.tencent.com/mobile-development-essential-knowledge.html

  4. C# 释放非托管资源

    C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放非托管资源的代码(释非 ...

  5. 6.C# 释放非托管资源2

    C# 释放非托管资源 C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放 ...

  6. Dispose模式释放非托管资源

    实现方式用的是设计模式里的模板模式,基类先搭好框架,子类重写void Dispose(bool disposing) 即可. 需要注意的是基类的Finalize函数也就是析构函数调用的是虚函数void ...

  7. [转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源:

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

  8. IDisposable?释放非托管资源接口

    原文:https://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html IDisposable高级篇:https://docs.micro ...

  9. 【Bugly 技术干货】Android开发必备知识:为什么说Kotlin值得一试

    1.Hello, Kotlin Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 1. ...

随机推荐

  1. Docker多主机管理(八)--技术流ken

    docker多主机管理 前面我们的实验环境中只有一个 docker host,所有的容器都是运行在这一个 host 上的.但在真正的环境中会有多个 host,容器在这些 host 中启动.运行.停止和 ...

  2. IO通信模型(一)同步阻塞模式BIO(Blocking IO)

    几个概念 阻塞IO 和非阻塞IO 这两个概念是程序级别的.主要描述的是程序请求操作系统IO操作后,如果IO资源没有准备好,那么程序该如何处理的问题:前者等待:后者继续执行(但是使用线程一直轮询,直到有 ...

  3. c#基础,单线程,跨线程访问和线程带参数

    using System; using System.Collections.Generic; using System.Threading; using System.Windows.Forms; ...

  4. PHP 中的CURL 模拟表单的post提交

    废话不多说啦,直接上代码: <?php $data = ['username'=>'乔峰','skill'=>'擒龙手']; $headers = array('Content-Ty ...

  5. windows安装elasticsearch

    1. 去官网 https://www.elastic.co/cn/ 下载 注意: ES对JDK版本有较高的要求,5.x及以上版本需要JDK8支持,本人此次使用2.4.6版本2. 下载后,JDK先安装, ...

  6. 常用的String原型

    对于常用的字符串原型的举例 在字符串末尾追加字符串 String.prototype.append = function (str) { return this.concat(str);} 删除指定索 ...

  7. 36.Odoo产品分析 (四) – 工具板块(6) – 午餐管理(2)

    查看Odoo产品分析系列--目录 接上一篇Odoo产品分析 (四) – 工具板块(6) – 午餐管理(1) 4 查看订单 点击"之前的订单",可以看到刚才的订单信息:  点击右边的 ...

  8. Java list.remove( )方法需要注意的地方

    List<Integer> integerList = new ArrayList<>(); 当我们要移除某个Item的时候 remove(int position):移除某个 ...

  9. 牛客网:java入门实现遍历目录

    项目介绍 遍历目录是操作文件时的一个常见需求.比如写一个程序,需要找到并处理指定目录下的所有JS文件时,就需要遍历整个目录.该项目教会你如何使用流式编程和lambda表达式,帮助你进一步熟悉java8 ...

  10. 第三篇 Html-label标签

    label标签 用户获取文字,使得关联的标签获取光标 <!DOCTYPE html> <html lang="en"> <head> <m ...