When to Use Releasecomobject VS Finalreleasecomobject

When to use ReleaseComObject vs FinalReleaseComObject?

There's some virtue in FinalReleaseComObject, it will crash your program quicker. "COM object that has been separated from its underlying RCW cannot be used" is the CLR telling you that you taking care of COM reference counts yourself instead of leaving it up the CLR was a mistake. Your mileage may vary, you cannot really trust to get it right when it works on your dev machine. Make sure you implement good error reporting when you deploy the code to your customer's machine.

The virtue is that there's only one place in your code where you got it wrong, the FinalReleaseComObject call. It gets much fuzzier when you use ReleaseComObject. Because that will go undetected for a while, crashing your program when the CLR calls the final IUnknown::Release(), the one that destroys the object. Very far removed from an incorrect ReleaseComObject call. But that's the doomsday scenario, the more likely outcome is that the call just doesn't make any difference because you missed the hard ones. Like mumble["foo"], an indexer reference that is so very hard to see being used.

Well, my advice is obvious: don't do this. You are competing with a machine that never gets it wrong. It is merely a bit slow at doing so. A very good "report from real life" is available here. The "silent assassin" section is most relevant.

If it is absolutely essential to get a COM server to exit instantly then let the machine take care of getting all the reference counts to 0. You do so with GC.Collect(). But do keep in mind that you have to place that call correctly if you want this to also work when you debug. It won't work in the same method that uses the COM objects, explained in this answer. Put it in the calling method instead.

As of today, what is the right way to work with COM objects?

The .NET / COM interop is well designed, and works correctly. In particular, the .NET Garbage Collector correctly tracks COM references, and will correctly release COM objects when they have no remaining runtime references. Interfering with the reference counts of COM object by calling Marshal.ReleaseComObject(...) or Marshal.FinalReleaseComObject(...) is a dangerous but common anti-pattern. Unfortunately, some of the bad advice came out of Microsoft.

Your .NET code can correctly interact with COM while ignoring all 5 of your rules.
If you do need to trigger deterministic clean-up of COM objects that are no longer referenced from the runtime, you can safely force a GC (and possibly wait for finalizers to complete). Otherwise, you don't have to do anything special in your code, to deal with COM objects.

There is one important caveat, that might have contributed to confusion about role of the garbage collector. When debugging .NET code, local variables artificially have their lifetime extended to the end of the method, in order to support watching the variabled under the debugger. That means you might still have managed references to a COM object (and hence the GC won't clean up) later than expect form just looking at the code. A good workaround for this issue (which only occurs under the debugger) is to split the scope of COM calls from the GC cleanup calls.

As an example, here is some C# code that interacts with Excel, and cleans up properly. You can paste into a Console application (just add a reference to Microsoft.Office.Interop.Excel):

using System;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace TestCsCom
{
class Program
{
static void Main(string[] args)
{
// NOTE: Don't call Excel objects in here...
// Debugger would keep alive until end, preventing GC cleanup

// Call a separate function that talks to Excel
DoTheWork();

// Now let the GC clean up (repeat, until no more)
do
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
while (Marshal.AreComObjectsAvailableForCleanup());
}

static void DoTheWork()
{
Application app = new Application();
Workbook book = app.Workbooks.Add();
Worksheet worksheet = book.Worksheets["Sheet1"];
app.Visible = true;
for (int i = 1; i <= 10; i++) {
worksheet.Cells.Range["A" + i].Value = "Hello";
}
book.Save();
book.Close();
app.Quit();

// NOTE: No calls the Marshal.ReleaseComObject() are ever needed
}
}
}

You'll see that the Excel process properly shuts down, indicating that all the COM objects were properly cleaned up.

VSTO does not change any of these issues - it is just a .NET library that wraps and extends the native Office COM object model.


There is a lot of false information and confusion about this issue, including many posts on MSDN and on StackOverflow.

What finally convinced me to have a closer look and figure out the right advice was this post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ together with finding the issue with references kept alive under the debugger on a StackOverflow answer.


One exception to this general guidance is when the COM object model requires interfaces to be released in a particular order. The GC approach described here does not give you control over the order in which the COM objects are released by the GC.

I don't have any reference to indicate whether this would violate the COM contract. In general, I would expect COM hierarchies to use internal references to ensure any dependencies on the sequence are properly managed. E.g. in the case of Excel, one would expect a Range object to keep an internal reference to the parent Worksheet object, so that a user of the object model need not explicitly keep both alive.

There may be cases where even the Office applications are sensitive to the sequence in which COM objects are released. One case seems to be when the OLE embedding is used - see https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/04/11/excel-ole-embedding-errors-if-you-have-managed-add-in-sinking-application-events-in-excel-2/

So it would be possible to create a COM object model that fails if objects are released in the wrong sequence, and the interop with such a COM model would then require some more care, and might need manual interference with the references.

But for general interop with the Office COM object models, I agree with the VS blog post calling "Marshal.ReleaseComObject – a problem disguised as a solution".

When am I required to call Marshal.ReleaseComObject on an interface queried through COM in C#

Almost never. ReleaseComObject manages the reference count of the RCW, not the underlying object and is not directly analogous to IUnknown.Release. You should let the CLR manage its QueryInterface'ing and Release'ing.

The RCW has a reference count that is incremented every time a COM interface pointer is mapped to it. The ReleaseComObject method decrements the reference count of an RCW. When the reference count reaches zero, the runtime releases all its references on the unmanaged COM object, and throws a System.NullReferenceException if you attempt to use the object further. If the same COM interface is passed more than one time from unmanaged to managed code, the reference count on the wrapper is incremented every time, and calling ReleaseComObject returns the number of remaining references.

...

This method enables you to force an RCW reference count release so that it occurs precisely when you want it to. However, improper use of ReleaseComObject may cause your application to fail, or may cause an access violation.

From http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx

FYI, the way to call IUnknown.Release directly is Marshal.Release, not ReleaseComObject.

Is it necessary to call Marshal.ReleaseComObject in C#4 when doing COM?

Marshal.ReleaseComObject provides a way to immediately drop references to this COM object from everywhere it is being consumed within managed code (because it releases the underlying IUnknown from the RCW that is holding it).

As Hans notes, the generally right course is to simply null your object allow the CLR and GC to do the COM object destruction at the appropriate time.

However, in situations where you need immediate action (COM object holds expensive/scarce resources, or perhaps during shutdown where the sequencing is very complex), calling ReleaseComObject can be the right thing to do.

Martyn

Do I absolutely need to call ReleaseComObject on every MSHTML object?

The RCW will release the COM object when the RCW is finalized, so you don't need to create a wrapper that does this. You call ReleaseComObject because you don't want to wait around for the finalization; this is the same rationale for the Dispose pattern. So creating wrappers that can be Disposed isn't a bad idea (and there are examples out there

For var doc = myBrowser.Document.DomDocument ...;, you should also capture .Document in a separate variable and ReleaseComObject it as well. Any time you reference a property of a COM object which produces another object, make sure to release it.

In GetAttribute, you're casting the element to another interface. In COM programming, that adds another reference. You'll need to do something like var htmlElement = (IHTMLElement) element; so you can release that as well.

Edit - this is the pattern to use when working with COM objects:

IHTMLElement element = null;
try
{
element = <some method or property returning a COM object>;
// do something with element
}
catch (Exception ex) // although the exception type should be as specific as possible
{
// log, whatever

throw; // not "throw ex;" - that makes the call stack think the exception originated right here
}
finally
{
if (element != null)
{
Marshal.ReleaseComObject(element);
element = null;
}
}

This should really be done for every COM object reference you have.

When do I need to call ReleaseComObject?

In short, every reference you make to a COM object must be released. If you don't, the process will stay in memory.

That excludes value types (strings, etc.) and child COM objects you have not explicitly referenced.

One exception may be COM objects passed to you as event parameters. I don't think you need to release them but I'm not certain. However a quick test should confirm that. (Try your add-in with and without releasing the COM object. See if the add-in begins to behave funny or if any related processes are left running after the app is closed.)

To address your specific questions:

  1. If we access the Document object do we need to call release on it? Or can we assume Word has already accessed it and will clean it up? -- You must release it.

  2. If we access Document.Name that gives us a string. As a string is not a COM object we do not need to clean that up - correct? -- You don't need to clean up value types.

  3. But if we access any member that returns a class that is wrapping a COM object (which is any class returned by a member method/function), we do need to call release on that - correct? -- You don't need to release it if you haven't explicitly referenced it. (There are some exceptions. For example in this question of mine I discovered that a particular COM method was instantiating a COM object and never releasing it. Since I have no control over the method implementation my only choice was to avoid using the method.)

  4. And what happens if we miss a release? -- The process (winword.exe, excel.exe, etc.) will remain in memory. Over time all of these unterminated processes will eat up all of the available memory on the machine.

  5. Are we better off having a finalizer which then adds overhead for every single instance of these objects (a lot!)? Or are we better off with a small number of Word COM objects that are never released? -- You're better off with the finalizer. Always release your COM objects! I've found that the steps outlined here are the most effective. (These steps use FinalReleaseComObject which is preferred over ReleaseComObject.) I also advise against killing the Office process as an alternative to not releasing COM. Over time this will cause issues with the Office interop (which I've never fully understood).

In what order should one release COM objects and garbage collect?

Marshal.FinalReleaseComObject() releases the underlying COM interface pointer.

GC.Collect() and GC.WaitForPendingFinalizers() causes the finalizer for a COM wrapper to be called, which calls FinalReleaseComObject().

So what makes no sense is to do it both ways. Pick one or the other.

The trouble with explicitly calling FinalReleaseComObject() is that it will only work when you call it for all the interface pointers. The Office program will keep running if you miss just one of them. That's very easy to do, especially the syntax sugar allowed in C# version 4 makes it likely. An expression like range = sheet.Cells[1, 1], very common in Excel interop code. There's a hidden Range interface reference there that you never explicitly store anywhere. So you can't release it either.

That's not a problem with GC.Collect(), it can see them. It is however not entirely without trouble either, it will only collect and run the finalizer when your program has no reference to the interface anymore. Which is definitely what's wrong with your second snippet. And which tends to go wrong when you debug your program, the debugger extends the lifetime of local object references to the end of the method. Also the time you look at Taskmgr and yell "die dammit!"

The usual advice for GC.Collect() applies here as well. Keep your program running and perform work. The normal thing happens, you'll trigger a garbage collection and that releases the COM wrappers as well. And the Office program will exit. It just doesn't happen instantly, it will happen eventually.



Related Topics



Leave a reply



Submit