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. React Fiber源码分析 (介绍)

    写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法 ...

  2. JS闭包作用域解析

    什么是闭包? 简单理解,当在一个函数的外部访问函数内部定义的变量的时候就会形成一个闭包,由这个理解可以知道,当一个函数执行完成的时候,一般情况下,其作用域会被销毁,其内部定义的变量也会变得不可访问,所 ...

  3. vb.net 分割byte数组的方法SplitBytes

    以下代码随手写的 并没有大量测试 效率也有待提升 如果需要C#的请自行转换 Function SplitBytes(Data As Byte(), Delimiter As Byte()) As Li ...

  4. Spring Boot统一异常处理实践

    摘要: SpringBoot异常处理. 原文:Spring MVC/Boot 统一异常处理最佳实践 作者:赵俊 前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多 ...

  5. vue 中promise 异步请求数据

    export function getTypes(type) { return listDictItems({ code: type }).then((res) => { if (res.cod ...

  6. 如何在WIN10内置Ubuntu中有多个terminal

    使用的是tmux来实现在WIN10的内置Ubuntu实现多终端窗口 先安装tmux:sudo apt-get install tumx 启动tmux,tmux 然后就可以在tmux中实现多窗口.其操作 ...

  7. Java内存管理的进一步理解-模拟过程图解

    Java内存管理的进一步理解-模拟过程图解--转载 java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 /* 1:方法区      方法区存放装载的类数据信息包括:      ...

  8. 深入了解Object.defineProperty

    原来写文章都是一次写两三个小时写完,偶尔看到一个人的博客了解到还有草稿箱这个功能,所以以后写文章的时候就舒服多了哈哈,可以存起来再发,不需要一口气写完了 最近一直在看JavaScript高级程序设计, ...

  9. c/c++ 多线程 参数传递

    多线程 参数传递 1,值传递,拷贝一份新的给新的线程.线程1中有个int变量a,在线程1中启动线程2,参数是a的值,这时就会拷贝a,线程1和线程2不共享a. 2,引用传递,不拷贝一份新的给新的线程.线 ...

  10. c/c++ linux 进程 fork wait函数

    linux 进程 fork wait函数 fork:创建子进程 wait:父进程等待子进程结束,并销毁子进程,如果父进程不调用wait函数,子进程就会一直留在linux内核中,变成了僵尸进程. for ...