Visual Studio 团队版首席开发工程师 Paul Harrington 撰文指出,在调用 Marshal.ReleaseComObject() 方法处理来自托管代码的 COM 对象时,会产生安全隐患并建议大家不要使用该函数。 Harrington 使用 Marshal.ReleaseComObject 方法把 Visual2010 部分组件、Windows 管理器、命令行栏和文本编辑器由本地代码转换成托管代码时发现了该问题。 VS 2005 和 2008 已用本地代码编写这些组件,他们过去也能正常运行,直到把它们转换为托管代码时才出现问题。
在托管代码要使用 COM 功能时,它会通过
COM Interop 来完成。在调用 COM 对象的时候,CLR 返回封装在
运行时可调用封装器 (RCW) 中的对象,它服从托管对象规则和垃圾回收规则(GC)。这表示在 GC 决定要清理时,就会释放 RCW。如果应用程序使用的资源不多,那么 GC 可能很迟都不会进行清理工作,甚至要到应用程序关闭才会采取行动。在那段时间内,RCW 也许会占用一个待处理的庞大 COM 对象,如果应用程序关闭且 GC 还没运行的话,该漏洞就会存在风险。
要避免遗留未处理的 COM 对象,COM Interop 已提供 Marshal.ReleaseComObject 方法,它降低 RCW 引用计数器的使用次数,该计算器用于计算有多少客户调用该对象。该方法返回引用次数的新值,该值“在运行时可调用封装器对封装的 COM 对象仅剩一个引用时通常为零,而不管当前托管客户端正调用它的次数”。与此同时,COM 对象消耗的底层资源也因此释放。
这个机制在Visual Studio 之前的版本非常奏效,Visual Studio 可多次调用Marshal.ReleaseComObject。 但是某些控件被重写为托管代码时却发生变化。为了保持与其余代码的兼容性,新组件经由
COM 可调用封装器通过 Interop 层进行访问。所以,调用器会认为自己正处理本地 COM 对象,而事实上它正使用托管对象。一切运行正常,直到调用 Marshal.ReleaseComObject 函数,它最初是用于释放 COM 资源的。运行时会抛出 ArgumentException 异常,提示信息为“该对象的类型必须是 __ComObject 或继承至 __ComObject”。问题在于要释放的是 COM 对象而非托管对象。
使用 Marshal.ReleaseComObject 还会产生另一个问题。调用该方法后,一般会释放 COM 资源并通常返回零。那表示 COM 对象由 RCW 封装器释放。如果其它客户要调用同样的 COM 对象,他就会得到
InvalidComObjectException 异常,提示信息为“COM 对象已从底层 RCW 中分离,不能使用”,这是由于缓存的 RCW 对象未被垃圾回收。那么,程序员必须确保特定的 COM 对象对 Marshal.ReleaseComObject 的调用不再被使用。
要在 VS2010 团队版中解决该问题必须禁用 Marshal.ReleaseComObject。他们还为此编写了 VS2005 和 VS2008 的托管包的框架修订版,以便在加载 VS2010 的时候不再遇到 ReleaseComObject 的问题。在使用 VS 2010 出现问题的时候,你会看到这些修订版在 Microsoft.VisualStudio.Shell 和 Microsoft.VisualStudio.Shell.9.0. 中绑定 devenv.exe.config 文件,这也许会普遍出现在使用 COM Interop 对象的项目当中。
评论