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

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.

  • How to properly release Excel COM objects (Nov. 2013)
  • Proper Way of Releasing COM Objects in .NET (Aug. 2011)
  • Marshal.ReleaseComObject Considered Dangerous (Mar. 2010)
  • ReleaseCOMObject (Apr. 2004)

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

  • MSDN - Marshal.ReleaseComObject Method (current .NET Framework version):

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

Solution 1:

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