16

This is a very common question and I decided to ask it because this question may have a different answer as of today. Hopefully, the answers will help to understand what is the right way to work with COM objects. Personally, I feel very confuse after getting different opinions on this subject.

The last 5 years, I used to work with COM objects and the rules were very clear for me:

  1. Use a single period in lines of code. Using more than one period create temporary objects behind the scene that cannot be explictly released.
  2. Do not use foreach, use a for loop instead and release each item on each iteration
  3. Do not call FInalReleaseComObject, use ReleaseComObject instead.
  4. Do not use GC for releasing COM objects. GC intent is mainly for debugging usage.
  5. Release objects in reverse order of their creation.

Some of you may be frustrated after reading those last lines, this is what I knew about how to properly create/release Com Object, I hope getting answers that will make it clearer and uncontested.

Following, are some links I found on this topic. Some of them telling that it is needed to call ReleaseComObject and some of them not.

"... In VSTO scenarios, you typically don’t ever have to use ReleaseCOMObject. ..."

"...You should use this method to free the underlying COM object that holds references..."

UPDATE:

This question has been marked as too broad. As requested, I will try to simplify and ask simpler questions.

  1. Does ReleaseComObject is required when working with COM Objects or calling GC is the correct way?
  2. Does VSTO approach change the way we used to work with COM Objects?
  3. Which of the above rules I wrote are required and which are wrong? Is there any others?
ehh
  • 3,412
  • 7
  • 43
  • 91
  • COM is COM, and as of today it remains COM. I don't think the responsability of resource release from the dev. side and its usage and administration while in .NET has changed/evolved in some way, anyways I'm not a unmanaged code gurú. However, is there a good reason why you have been applying the 3rd rule during all those years?. The `FinalReleaseComObject` is supposed to release all the RCW's reference count at once, avoiding redundant single calls to `ReleaseCOMObject`. See this answer: http://stackoverflow.com/questions/1827059/why-use-finalreleasecomobject-instead-of-releasecomobject – ElektroStudios Jun 19 '16 at 07:23
  • 2
    @ElektroStudios, indeed FinalReleaseComObject will release all the RCW's reference count at once but for better management, debugging purpose, I preferred using ReleaseComObject. Using FinalReleaseComObject may quickly crash you program with the "Com Object separated from its underlying RCW" message. – ehh Jun 19 '16 at 07:40
  • Why do you need to manually release the COM-objects at all instead of relying on the GC to eventually clean up? e.g. do the COM-objects hold limited resources like file handlers etc. – adrianm Jun 20 '16 at 08:37
  • 1
    @adrianm because COM objects usually are unmanaged code, and GC cannot reach their ressources... – Martin Verjans Jun 20 '16 at 09:31
  • @SuperPeanut, What do you mean? The GC will (sooner or later) release the COM objects which hopefully manages its' own (unmaganed-) resources. My question is why OP need to release the COM objects directly instead of waiting for the GC? I know there are reasons for a quick release but in the general case I would say "Don't do it unless you are in a situation where late release is a problem". – adrianm Jun 20 '16 at 13:43
  • 1
    .Net V4.0 introduced [Marshal.AreComObjectsAvailableForCleanup](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.arecomobjectsavailableforcleanup(v=vs.110).aspx). I gave an example in [this answer](http://stackoverflow.com/a/36578663/2592875). – TnTinMn Jul 04 '16 at 01:40
  • @TnTinMn That's a useful find - I've added your suggestion to the code in my answer. – Govert Jul 04 '16 at 12:09

1 Answers1

28

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".

Govert
  • 16,387
  • 4
  • 60
  • 70
  • 1
    10 calls to prove the concept, seriously? Come on, at least try with 10M :) – Ivan Stoev Jul 03 '16 at 16:33
  • @IvanStoev My patience for this topic has worn rather thin already. – Govert Jul 03 '16 at 17:00
  • 3
    I would add that there are many COM objects out there written in unmanaged code that do have bugs (not talking about Microsoft's big ones which are in general ok). These bugs contribute to the false impression that P/Invoke is broken, as they sometimes force .NET developer to apply COM panic voodoo magic (add ReleaseComObject everywhere, release objects in reverse order, etc.) – Simon Mourier Jul 04 '16 at 21:10
  • @SimonMourier, your comment helped me to understand my confusion. When I said that the last 5 years I used to ReleaseComObject it was when our team was working on the very large project and the unmanaged code was not Microsoft but a core API developed by another team. Without Releasing COM objects, we got lot of crashes and we though that this is the way to properly working with COM objects. In addition to Govert answer, I feel ready to adopt the recommended way to work with COM. – ehh Jul 05 '16 at 06:00
  • 2
    I know this is rather old, but I'm curious how much of this advice is applicable to COM interop code for, say, (not?) cleaning up dockable toolwindows in a VBIDE add-in, where the managed .net code is hosted by a COM application, as opposed to managed .net code *creating and destroying* a bunch of COM objects. – Mathieu Guindon May 01 '17 at 14:25
  • @Govert - a very old post, I know - but I'm struggling with exactly the contents of this thread at the moment. I have stepped back from my actual application, and run your exact code - which leaves an instance of Excel open for me in task manager. .NET 6. Are you aware of any breaking changes? – jwilo Dec 05 '22 at 01:06
  • @Govert I've opened a question for exactly this https://stackoverflow.com/questions/74682442/why-do-instances-of-excel-not-close-when-opened-via-interop-even-though-word-ap. If you could spare a couple of minutes to take a look, I'd greatly appreciate it! – jwilo Dec 05 '22 at 02:14