When to release COM objects in Office add-ins developed in .NET
I like to work at the support desk. I’ve become much closer to developers’ needs and musts. They hit me with releasing COM objects in COM add-ins written VB.NET and C#. I've collected most common questions and my answers on this page. Well, let’s go!
Q: Is releasing COM objects required in Outlook add-ins only? What about other Office applications? What about IE?
A: It is necessary to release COM objects of every Office application. This is especially true for Office 2000 and 2002 versions. This also relates to the Office object model (it contains in Office.dll), which provides COM classes for COM add-ins, command bars, workspaces, task panes, etc. Speaking about IE, it looks like IE was developed so that non-released COM objects don’t affect IE functioning. However, I can’t find a confirmation of this on Microsoft’s web sites. A long while ago, we faced some problems in Excel, Word, and other applications when we didn’t release COM objects. Since then, our rule of thumb is: release every COM object you use in any programming language: C#, VB.NET, C++, or Delphi.
Q: I use Excel, not Outlook. Why should I release COM objects if I do not release them and my add-in works?
A: The reaction to not releasing a COM reference depends on the implementation of the corresponding COM server. Some COM servers, Internet Explorer, for instance, allow this, some do not. Say, Office generally allows, but there is a number of exceptions. It is great that your add-in works. But there is no guarantee that your add-in will work after installing some service pack or update from Microsoft. They fix bugs. They patch security holes in Office applications. In short, Microsoft change their code. You cannot be sure that your code will work next month. Release COM objects, be happy. Releasing COM objects is a boring, yet safe practice. I was taught the “Holy Rule” – when leaving, restore the state, weren’t you?
Q: Should I release explorer in the following VB.NET code:
Private Sub adxOutlookEvents_ExplorerSelectionChange( _ ByVal sender As System.Object, _ ByVal explorer As System.Object) _ Handles adxOutlookEvents.ExplorerSelectionChange '... End Sub
A: Please never release COM objects passed in parameters of Add-in Express events. Add-in Express relies on the state of every COM object passed to you in event parameters. Usually, such parameters are released just after the event handler finishes. But there are numerous exceptions. Just recently, we saw how a customer released the inspector passed to the NewInspector handler in the Dynamic Command Bar and Controls in Outlook sample add-in. This caused adding command bars to an explorer window instead of inspector’s one. Bizarre, this happened in a certain scenario only.
Q: Should I release explorerObj in the following VB.NET code:
Private Sub adxOutlookEvents_ExplorerSelectionChange( _ ByVal sender As System.Object, _ ByVal explorer As System.Object) _ Handles adxOutlookEvents.ExplorerSelectionChange Dim explorerObj As Outlook.Explorer = _ CType(explorer, Outlook.Explorer) '... End Sub
A: In .NET, every COM object is wrapped by a run-time callable wrapper, RCW. So, whenever you create a given COM object and assign it to a variable, you create the following chain: the COM object itself, an RCW referencing the COM object, and the .NET variable referencing the RCW. When you assign the variable to another one, both of them refer to the same RCW. That is, both explorer and explorerObj in the code above refer to the same COM object that was passed in the event’s parameters. So, you mustn’t release explorerObj.
Q: Does assigning a COM object reference to a variable increase the reference counter for the COM object?
A: No, it doesn't. The reference counter is managed by a corresponding RCW. And the RCW isn’t involved when you assign one .NET variable to another. For that reason, you can pass a .NET variable referencing a COM object to any method and release the COM object there. Note however, that all .NET variables that refer to that COM object will produce an error if you use them after releasing one of them. The error message is “COM object that has been separated from its underlying RCW cannot be used”.
Q: I wrote a VB class that processes mail items. I plan to use it in the NewInspector event. But I don’t understand how to release the corresponding COM object. If I release it in the way below, then my class fails with “COM object that has been separated from its underlying RCW cannot be used”. What should I do?
Dim mailItem As Outlook.MailItem = Nothing Dim inspectorObj As Outlook.Inspector = _ CType(inspector, Outlook.Inspector) Try mailItem = CType(inspectorObj.CurrentItem, Outlook.MailItem) myObject = New MyClass(mailItem) ' Do stuff Catch ex As Exception ' Handle errors Finally If (mailItem IsNot Nothing) Then Marshal.ReleaseComObject(mailItem) End If End Try
A: Well, the constructor of the MyObject class stores the COM object for some later use. Then, after the constructor finishes, the COM object is released in the Finally block. Naturally, when you access the COM object, .NET Framework generates the exception above. That is, with the current code, you can only use this COM object between creating myObject and the Finally block. To prolong its life so that you can use it later, until the user closes the item, you can release mailItem in MyClass, not in the event. Note however, that this approach requires handling all item events that close the inspector: Close, Send, Reply, ReplyAll, and Forward. This can add excessive complexity to your code. We recommend using “short transactions”. Get the COM object, use it, and release it. Get the COM object anew, use it, and release it again.
Q: How can I learn whether a given COM object is released or not?>
A: There are two ways. You can try accessing any property of the COM object. If the COM object is released, you’ll get the “COM object that has been separated from its underlying RCW cannot be used” exception. The other way is checking the value returned by Marshal.ReleaseComObject. If it is zero, well, the object is released. Otherwise, it returns the number of references for this COM object.
In Add-in Express code, we always use the following approach: whenever we apply ReleaseComObject to some .NET variable, we set the variable to null. This allows us to use null as an indicator: if a variable is null (Nothing in VB) then it is released. If it isn’t null, this means that the correspnding COM object was not released earlier.
Also, in Microsoft samples, no COM objects are released. Then why should I release them?
A: I’m sure that you use Office 2003 or 2007. If you were using Outlook 2000 or 2002 (XP), you wouldn't certainly ask this question because these Outlook versions hang with such code. And all applications from Office versions 2003 and 2007 are able to release “hanging” COM references. Some references, but not all. For instance, in the above-mentioned “Outlook does not close” post, I show how to hang any Outlook version just by not releasing ActiveExplorer.
In your case, I wouldn’t be surprised that after the code above runs, the corresponding inspector window stays in the Inspectors collection even after the user closes it. That’s because in such cases, the inspector closes only visually. An indication of such a scenario might be the user being unable to save an edited item.
Also note, that by chaining COM objects in this way, you loose references to corresponding RCWs and the RCWs live for as long as another run of the GC.
Q: Does the order of releasing matter?
A: It looks like it doesn't matter. Nevertheless, we prefer to release them in the order which is backward to the order of getting them. Say, if we get Inspector, then CurrentItem, then Attachments and then an Attachment, we release them in the reverse order: Attachment, Attachments, Item, Inspector. This is just a precaution, nothing else. To be precise, we have never seen negative effects when releasing them in an arbitrary order.
Q: What are the consequences of not releasing some COM object? I develop an Outlook add-in in VSTO. Does the release-every-COM-object stuff apply to me?
A: Mostly, there are no consequences at all. But if they are, it is usually hard to imagine that this behavior might be caused by a COM object which wasn’t released. In an article published at CodeProject.Com (see Developing COM Add-ins for Microsoft Office in VB.NET), I demonstrate the following scenario:
- The user opens an inspector for an e-mail, appointment item, etc.
- The add-in handles either the Inspectors.NewInspector or Inspector.Activate event to get a reference to the item displayed in the inspector.
- The user changes, for example, the item’s subject and clicks Save.
- The add-in processes the Item.Write event and cancels the operation.
- The user closes the inspector without saving the changes and the inspector window gets closed.
- The add-in doesn’t release the item in the Inspector.Close event; the corresponding Inspector object remains in the Inspectors collection waiting for the next run of the GC.
As a result, the user can see the item’s subject unchanged in the explorer window but, when the user opens it anew, the hidden inspector pops up with the e-mail subject still reflecting the changes that have just been canceled.
When something of this kind happens, I always check whether the Inspectors collection contains some items that aren’t visible in the Outlook UI. If they exist, well, I start checking the COM objects used in the code. In the above-mentioned article, I recommend using the VBA Object Browser for this.
In VSTO, they make an impression that you can develop without Marshal.ReleaseComObject by wrapping everything and killing the AppDomain. But this is just an impression. The scenario above applies to VSTO development as well.
Q: Can I use the Garbage Collector to release COM objects?
A: The Garbage Collector (GC) does release COM objects if the corresponding .NET objects are not referenced (if they are set to null or Nothing). So you can use it to release unused COM objects. There are some drawbacks, though. First, it is an implicit way. That is, you can’t control the result. Secondly, you have to run the GC spending some time on this. More than that, the way in which .NET objects are stored in memory requires you to run the GC twice. Have you ever seen the magic sequence below?
GC.Collect GC.WaitForPendingFinalizers GC.Collect GC.WaitForPendingFinalizers
Now, you might want to read When to call GC.Collect() at Rico Mariani’s Performance Tidbits where you find why it isn’t recommended to call GC.Collect().
I would agree to using GC.Collect in the BeginShutdown event. That is, at the end of job. This could increase the time required for the host application to close which doesn’t matters for most add-ins. This will do for an add-in that acquires a single COM object at start-up and uses it all through its lifetime. But add-ins are usually more complex than that. And a COM object which isn’t released at an appropriate moment can interfere with the host application itself. If the best happens, this makes your add-in useless in some scenarios. At worst, it makes the everyday work of the user impossible and it turns off your add-in.
Q: How to determine if this or that property or method returns a COM object?
A: By using either Marshal.IsComObject() or the VBA Object Browser. The VBA object browser window on the screenshot below shows two object types in the left pane: classes and enumerations. On the right pane, it shows properties, methods, and events of the item selected on the left pane. It is the Views collection from the Outlook object model, in our case.
The bottom pane shows the VBA syntax for the currently selected member of the current class. Note that the Item method returns a value of the View type. At this stage, no problems usually arise because you can see that the return value is of a type described in the object model.
But note that the Index parameter doesn’t have any type at all. In VBA, this case follows the convention: if no type is specified for a variable, then the variable is of the Variant type. If you ever run across a VBA function that is described as non-returning anything, know that it returns a Variant. Now, what is Variant?
In the COM world (and VBA is a COM-based language), Variant is almost the same as Object is in the .NET world: variables of this type can accept any other types. So, to understand if this or that Variant parameter or return value is a COM object, you need to refer to the Help reference. In the Views.Items method above, the Index parameter accepts integer or string values, so it isn’t a COM object.
If you choose the Views.Parent property in the VBA object browser, you’ll see that it returns Object.
Now, what is Object in VBA? It denominates any object-type reference in VBA. This reflects a VBA oddity: it differentiates between regular variables and variables of any object type. So, if the VBA object browser shows that a class’ member returns or accepts an Object, then it is a COM reference with 100% probability.
Q: What is the item leak that I've heard about?
A: This is an Outlook-specific result of non-releasing COM objects. It’s described at this MSDN blog. Outlook re-uses an item if it is loaded in memory. If you don’t release the item, the Outlook user that works with the item in the UI may not be able to save changes. You may also get an item leak, if you don’t release the item’s children: Attachments, UserProperties, Recipients, etc. Another way of provoking item leaks is using a foreach loop. In fact, in such loops you have a number of item leaks at once. Use for loops instead.
Q: You keep talking about releasing COM objects while Microsoft doesn’t recommend using it. Why?
A: We've got tired of that stuff. It looks like periodically Microsoft want to bury ReleaseComObject but it reemerges every time. A great number of problems solved using ReleaseComObject allows us (as well as other players in the field) to say: the best practice is to always release everything. Microsoft have the whole programming world in their mind. We talk only about COM add-ins that work with Office applications. Do you see the difference? It is dictated by the area of development. COM add-ins are COM add-ins because they were designed to live in the COM world. Therefore we all must follow the main rule of COM: release every resource you access in your code. Again, the consequence of not releasing a COM object depends on the server’s implementation.
Of course, I agree when Microsoft say “Marshal.ReleaseComObject adds a certain amount of complexity to the code” and “you must be certain that when you release an RCW, you will not attempt to reuse it anywhere else in your code”. But it was Microsoft who invented .NET and they knew that changing the way of releasing objects would be a big problem for developers. What can we do? Just live with it.