{"id":2413,"date":"2010-03-01T02:12:00","date_gmt":"2010-03-01T02:12:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/visualstudio\/2010\/03\/01\/marshal-releasecomobject-considered-dangerous\/"},"modified":"2022-10-17T12:38:20","modified_gmt":"2022-10-17T19:38:20","slug":"marshal-releasecomobject-considered-dangerous","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/visualstudio\/marshal-releasecomobject-considered-dangerous\/","title":{"rendered":"Marshal.ReleaseComObject Considered Dangerous"},"content":{"rendered":"<p>This post describes a problem we encountered and solved during the development of Visual Studio 2010 when we rewrote some components in managed code. In this post I\u2019ll describe the problem and what we did to solve it. This is a rather lengthy technical discussion and I apologize in advance for its dryness. It\u2019s not essential to understand every detail but the lesson we learned here may be valuable for others working with large \u201clegacy\u201d code bases.<\/p>\n<h3>What changed?<\/h3>\n<p>As I mentioned in the Background, we rewrote several components for Visual Studio 2010. Specifically, the window manager, the command bars and the text editor. In previous versions of Visual Studio, these were native components written in <a href=\"http:\/\/msdn.microsoft.com\/en-us\/visualc\/default.aspx\">C++<\/a>. In Visual Studio 2010, each was rewritten in <a href=\"http:\/\/en.wikipedia.org\/wiki\/Managed_code\">managed code<\/a> using <a href=\"http:\/\/msdn.microsoft.com\/en-us\/vcsharp\/default.aspx\">C#<\/a>.<\/p>\n<p><a href=\"http:\/\/visualstudiogallery.msdn.microsoft.com\/\">Extensions<\/a> which plug into Visual Studio communicate with these components through <a href=\"http:\/\/www.microsoft.com\/com\">COM<\/a> interfaces. Moving to managed code doesn\u2019t change how these extensions communicate with platform components. Indeed, that\u2019s the promise of <a href=\"http:\/\/www.bing.com\/search?q=interface+based+programming\">interface based programming<\/a> \u2013 i.e. you don\u2019t need to know implementation details in order to communicate with a component via its interface. In the case of the new editor, it so happens that we introduced a new, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd885244(VS.100).aspx\">managed programming model<\/a> for new extensions, but even so, we had to keep the existing COM interfaces for older extensions.<\/p>\n<p>Managed code and COM are brought together through the magic of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/kew41ycz(VS.100).aspx\">COM Interop<\/a>. Briefly, this allows two things to happen:<\/p>\n<ol>\n<li>Managed code can make calls through a COM interface just as if they were a regular method calls.<\/li>\n<li>Managed classes can implement COM interfaces so that they may be called as COM objects from either native or managed code.<\/li>\n<\/ol>\n<p>Let\u2019s take each of these in turn. (If you already know how COM Interop works, you can skip the following two sections.)<\/p>\n<h3>Calling COM objects from Managed code<\/h3>\n<p>The <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ddk909ch(VS.100).aspx\">Common Language Runtime<\/a>, CLR or just the \u201cRuntime\u201d, can make a COM object look just like a regular managed object. This is a special kind of object called a \u201c<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/8bwh56xe(VS.100).aspx\">Runtime Callable Wrapper<\/a>\u201d or RCW. RCWs bridge the managed, garbage-collected world with the native, ref-counted world. An RCW is created when \u201can IUnknown enters the runtime\u201d (<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms680509(VS.85).aspx\">IUnknown<\/a> is the minimum interface that all COM objects <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms686590(VS.85).aspx\">must implement<\/a>). When does that happen? Usually, as the result of an interop call to a native method which hands back a COM interface. In fact, typically, it\u2019s the result of method call on an existing COM object. Since that sounds a little bit like a \u201c<a href=\"http:\/\/en.wikipedia.org\/wiki\/Chicken-and-egg_problem\">chicken and egg problem<\/a>\u201d, let me give a concrete example. At the heart of the Visual Studio platform lies the Global Service Provider. This service provider keeps track of services offered up (\u201c<a href=\"http:\/\/encarta.msn.com\/dictionary_1861736695\/proffer.html\">proffered<\/a>\u201d) by components in the system. Other components can request a service by calling the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc678966(VS.85).aspx\">IServiceProvider.QueryService<\/a> method on the Global Service Provider object. If successful, the service returned to the caller will be another COM object, identified by a pointer to its IUnknown interface. If the component making the QueryService call is managed then, at the point where that pointer enters the Runtime, an RCW is created for the service. Of course, this still begs the question: \u201cHow did the managed component get hold of the Global Service Provider?\u201d. The answer is that the Global Service Provider was passed to the managed component by the platform when that component was first <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/microsoft.visualstudio.shell.interop.ivspackage.setsite(VS.100).aspx\">initialized<\/a>.<\/p>\n<h3>Implementing COM interfaces on managed objects<\/h3>\n<p>The tools and the Runtime make this very easy. To implement a COM interface on a managed object, you first need to locate or create an <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aax7sdch(VS.100).aspx\">interop assembly<\/a> containing the managed equivalent of that COM interface. By referencing that interop assembly from managed code and writing classes which implement those interfaces, you create COM compatible managed classes. There are a few other requirements (e.g. your classes must also be marked as <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.runtime.interopservices.comvisibleattribute(VS.100).aspx\">COM visible<\/a> either at the assembly level or on a per class basis), but otherwise it\u2019s straightforward. When an instance of once of these classes is passed through the interop layer to native code, the CLR creates a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/f07c8z1c(VS.100).aspx\">COM Callable Wrapper<\/a> or CCW. The CCW, among other things, preserves all the COM rules about identity and the lifetime of the wrapped object. For example, for as long as at least one native component holds a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/4947zb56.aspx\">reference<\/a> on the CCW, then the underlying managed object cannot be claimed by the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/0xy59wtx.aspx\">Garbage Collector<\/a>, even if there are no other managed roots. As far as the native code is concerned, it deals with an <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms680509(VS.85).aspx\">IUnknown<\/a>, unaware that the object is really a managed object.<\/p>\n<h3>Marshal.ReleaseComObject \u2013 a problem disguised as a solution<\/h3>\n<p>With that rather lengthy (my apologies) recap of COM Interop out of the way, let me describe the problem. Imagine, for the sake of a simplified example, that you have a component called the \u201cText Manager\u201d. The Text Manager, as you might guess, handles requests about textual things in an editor. Other components communicate to the text manager via the ITextManager interface with methods such as \u201cGetLines\u201d, or \u201cHighlightWord\u201d. ITextManager is a COM interface. Now imagine that there\u2019s a second component that implements a \u201cSearch\u201d facility for finding words in a document. The Search component is written in managed code. Obviously, this Search component will need access to the Text Manager to get its job done, and I\u2019m going to lead you through the scenario of performing a \u201cFind\u201d \u2013 once when the Text Manager is implemented in native code, and a second time when the Text Manager is managed.<\/p>\n<p>The \u2018find\u2019 operation begins with the Search component asking for the Text Manager service via the Global Service Provider. This succeeds and the Search Manager gets back a valid instance of ITextManager. Since, in this first walkthrough, the Text Manager is a native COM object, the IUnknown returned is wrapped by the runtime in an RCW. As far as the Search Manager is concerned, though, it sees ITextManager. It doesn\u2019t know or care (yet) whether the actual implementation is native or managed. The find operation continues with the Search component making various calls through ITextManager to complete its task. When the task is done, the \u2018find\u2019 operation exits and life is good. Well\u2026 almost. The ITextManager is an RCW and, as such it has the same kind of lifetime semantics as any other managed object \u2013 i.e. it will be cleaned up as and when the Garbage Collector runs. If there\u2019s not much memory pressure in the system, then the Garbage Collector may not run for a long time \u2013 if at all \u2013 and here is where the native and managed memory models clash to create a problem. You see, as far as the Search component is concerned it\u2019s finished with the Text Manager \u2013 at least until the next \u2018find\u2019 operation is requested. If there were no other components needing the Text Manager, now would be a great time for the Text Manager to be cleaned up. Indeed, if the Search component were written in native code, at the point of exiting the \u2018find\u2019 routine, it would call \u201cRelease\u201d on the ITextManager to indicate that it no longer needs the reference. Without that final \u201cRelease\u201d, it looks like a reference counting leak of the Text Manager \u2013 at least until the next garbage collection. This is a special, though not unusual case of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms235315(VS.80).aspx\">non-deterministic finalization<\/a>.<\/p>\n<p>This is just an example, but situations just like it really happened many times during Visual Studio 2005 and 2008 development. The bug reports would say that \u2018expensive\u2019 components were being reported as leaked objects, usually at shutdown. The &#8220;solution\u201d, as a few people discovered, was to insert a call to \u201c<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.runtime.interopservices.marshal.releasecomobject.aspx\">Marshal.ReleaseComObject<\/a>\u201d at the point where the expensive component (the Text Manager in our example) was no longer needed. The RCW is released, causing its internal reference count to drop by one and, typically releasing the underlying COM object. No more leaked references and problem solved! Well, at least for now, as we\u2019ll see. Regretfully, once this \u201csolution\u201d appeared in the source code of a few components, it spread rapidly as the \u2018quick fix\u2019 for leaked components and that\u2019s how we shipped. The trouble started when we began migrating some components from native code to managed code in VS 2010.<\/p>\n<p>To explain, I\u2019ll return to the \u2018find\u2019 scenario, this time with the Text Manager written in managed code. The Search component, as before, requests the Text Manager service via the Global Service Provider. Again, an ITextManager instance is returned and it\u2019s an RCW. However, this RCW is now a wrapper over a COM object which is implemented in managed code \u2013 a CCW. This double wrapping (an RCW around a CCW) is not a problem for the CLR and, indeed, it should be transparent to the Search component. Once the \u2018find\u2019 operation is complete, control leaves the Search component and life is good. Except that, on the way out the Search component still calls \u201cMarshal.ReleaseComObject\u201d on the ITextManager\u2019s RCW and, \u201coops!\u201d we get an ArgumentException with the message \u201cThe object&#8217;s type must be __ComObject or derived from __ComObject.\u201d. You see, the CLR is able to see through the double-wrapping to the underlying component and figure out that the it is really a managed object.<\/p>\n<p>There\u2019s really no workaround for this except to find all the places where \u201cReleaseComObject\u201d was called and remove them. Some have suggested that, before calling ReleaseComObject we should check first if it\u2019s going to succeed by calling \u201c<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.runtime.interopservices.marshal.iscomobject(VS.100).aspx\">Marshal.IsComObject<\/a>\u201d but, as we\u2019ll see in the next section there is another, more insidious problem still lurking.<\/p>\n<h3>Marshal.ReleaseComObject &#8211; the silent assassin<\/h3>\n<p>For this second problem, we\u2019ll return to our original example, with the Text Manager implemented in native code. Even with the \u2018safeguard\u2019 of Marshal.IsComObject, the Search component calls ReleaseComObject and goes on its way. However, the RCW has now been poisoned. As far as the CLR is concerned, by calling ReleaseComObject, the program has declared that the RCW is no longer needed. However, it\u2019s still a valid object, and that means it may be reachable from other managed code. If it is reachable, then the next time ITextManager is accessed from managed code through that RCW, the CLR will throw an <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.runtime.interopservices.invalidcomobjectexception.aspx\">InvalidComObjectException<\/a> with a message of \u201cCOM object that has been separated from its underlying RCW cannot be used\u201d.<\/p>\n<p>How can that happen? There are several ways &#8211; some common and some subtle. The most common case of attempting to re-use an RCW is when the services are cached on the managed side. When services are cached, instead of returning to the Global Service Provider each time the Text Manager (for example) is requested, the code first checks in its cache of previously requested services, helpfully trying to eliminate a (potentially costly) call across the COM interop boundary. If the service is found in the cache, then the cached object (an RCW) is returned to the caller. If two components request the same service, then they will both get the same RCW. Note that this \u2018cache\u2019 doesn\u2019t have to be particularly complicated or obvious \u2013 it can be as subtle as storing the service in a field (member variable) for later use.<\/p>\n<p>I\u2019ve called this use of Marshal.ReleaseComObject the \u201csilent assassin\u201d because, while the problem occurs at the point of the call to ReleaseComObject, it is not detected until later when another component innocently tries to access the poisoned RCW. At first glance, it appears that the second component has a bug, but it does not \u2013 the component that called ReleaseComObject is the assassin and \u2018he has left the room\u2019.<\/p>\n<p>The lesson here is: If you\u2019re tempted to call \u201cMarshal.ReleaseComObject\u201d, can you be 100% certain that no other managed code still has access to the RCW? If the answer is \u2018no\u2019, then don\u2019t call it. The safest (and sanest) advice is to avoid Marshal.ReleaseComObject entirely in a system where components can be re-used and versioned over time. While you may be 100% certain of the way the components work today and believe that a \u2018poisoned\u2019 RCW could never be accessed, that belief may be shattered in the future when some of those components\u2019 implementations change.<\/p>\n<h3>Fixing the mistake in Visual Studio 2010<\/h3>\n<p>In VS 2010, we scrubbed our code for instances of Marshal.ReleaseComObject and asked component authors to either remove or justify each occurrence. In our own code we found many instances, including in common library code used by managed packages. We were so concerned about the problem of running these components that we actually created patched versions of our Managed Package Framework for VS 2005 and VS 2008 so that, when loaded in VS 2010 they would not have ReleaseComObject problems. You\u2019ll see these patched versions appear as binding redirects in \u201cdevenv.exe.config\u201d for Microsoft.VisualStudio.Shell and Microsoft.VisualStudio.Shell.9.0.<\/p>\n<h3>What\u2019s Old is New Again<\/h3>\n<h3><\/h3>\n<p>Microsoft Distinguished Engineer, Chris Brumme, <a href=\"https:\/\/review.learn.microsoft.com\/en-us\/archive\/blogs\/cbrumme\/releasecomobject?branch=main\">offered some sage advice<\/a> about Marshal.ReleaseComObject back in 2003. It\u2019s worth a read because it shows that we were thinking about this problem way back then. In case it isn\u2019t obvious, Visual Studio is in category #2 on Chris\u2019 list at the end of the post.<\/p>\n<p>Mason Bendixen\u2019s Blog also has a nice collection of notes on COM interop and, in particular, <a href=\"https:\/\/learn.microsoft.com\/en-us\/archive\/blogs\/mbend\/the-mapping-between-interface-pointers-and-runtime-callable-wrappers-rcws\">this one on RCWs<\/a> is germane because it talks about the per-AppDomain mapping of IUnknowns to RCWs.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/4\/2019\/06\/clip_image002_2.jpg\"><img decoding=\"async\" title=\"clip_image002\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2010\/03\/clip_image002_thumb-4.jpg\" alt=\"clip_image002\" width=\"101\" height=\"101\" align=\"left\" border=\"0\" \/><\/a><\/p>\n<p><strong>Paul Harrington<\/strong> \u2013 Principal Developer, Visual Studio Platform Team\n<strong>Biography: <\/strong>Paul has worked on every version of Visual Studio .Net to date. Prior to joining the Visual Studio team in 2000, Paul spent six years working on mapping and trip planning software for what is today known as Bing Maps. For Visual Studio 2010, Paul designed and helped write the code that enabled the Visual Studio Shell team to move from a native, Windows 32-based implementation to a modern, fully managed presentation layer based on the Windows Presentation Foundation (WPF). Paul holds a master\u2019s degree from the University of Cambridge, England and lives with his wife and two cats in Seattle, Washington.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post describes a problem we encountered and solved during the development of Visual Studio 2010 when we rewrote some components in managed code. In this post I\u2019ll describe the problem and what we did to solve it. This is a rather lengthy technical discussion and I apologize in advance for its dryness. It\u2019s not [&hellip;]<\/p>\n","protected":false},"author":13,"featured_media":255385,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[155],"tags":[5,137],"class_list":["post-2413","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-visual-studio","tag-csharp","tag-html"],"acf":[],"blog_post_summary":"<p>This post describes a problem we encountered and solved during the development of Visual Studio 2010 when we rewrote some components in managed code. In this post I\u2019ll describe the problem and what we did to solve it. This is a rather lengthy technical discussion and I apologize in advance for its dryness. It\u2019s not [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/2413","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/comments?post=2413"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/2413\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media\/255385"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media?parent=2413"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/categories?post=2413"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/tags?post=2413"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}