2

Coming from an Excel VBA background I would frequently write code such as:

Range("myRange").Offset(0, 1).Resize(1, ccData).EntireColumn.Delete

I'm now moving to VSTO, and have been reading about RCW counters, etc., and the need to explicitly release COM objects. The basic advice seems to be: don't chain together references to Excel objects (as I have above) - hence "one dot good, two dots bad". My question is, am I correct that the above code is not the way to go in VSTO? If so, does that mean that I would need to explicitly declare the 3 ranges implied in the above chain (Offset, Resize & EntireColumn)?

Or even how about something like:

rng.Columns.Count

where rng is a declared Range? Should I be assigning a name to rng.Columns in order to obtain the number of columns in the range??

COG
  • 291
  • 2
  • 12
  • 1
    Depends, if you are making an Excel add-in via VSTO in .NET, the double dot warning won't apply because the COM references are in-process. Excel exited fine for me and disappeared from Task Manager even with my double-dot-using add-ins. I'm actually surprised RCW has this limitation. I would do `x.y.z[n]` all the time in c++ out-of-process COM no problems –  Mar 16 '15 at 00:29
  • Thanks. There seems to be a lot of contradictory advice out there. Sometimes that all COM objects should be declared and released (in a finally block), but **don't** use FinalReleaseComObject. Now I just read on MSDN: "use the ReleaseComObject only if it is absolutely required...consider using the FinalReleaseComObject method instead." – COG Mar 16 '15 at 15:08
  • 1
    I prefer your answer, though(!). What I'm developing is actually an Excel application built around an Excel workbook, rather than an add-in. Does that make a difference to your answer? I ended up down this rabbit hole after Excel started crashing on me. I guessed it was caused by memory issues caused by unreleased COM objects. – COG Mar 16 '15 at 15:12
  • Thanks for the additional info. Both Excel Workbook extension and Excel add-in project types are implemented as in-process COM servers (.dll) that Excel will instantiate. Because they are in-process, memory will be freed automatically when Excel exits. `Use double-dot to your hearts content - it won't matter and is completely safe`. **Don't use** `Marshal.ReleaseComObject()`. Double-dot in-process `won't lead to Excel crashing`. All those interim objects those doom-Sayers are yelling about `will be released during the next .NET Garbage Collection`. –  Mar 16 '15 at 23:40
  • Slightly off-topic - I just checked, if you make say a `WinForms` app that launches Excel (Ole Automation Server) via COM, you can safely use double dot in your WinForms code as well. When you are done either wait for the next GC to free-up memory and thus quit Excel or to force the issue do a `GC.Collect(); GC.WaitForPendingFinalizers();` and Excel will vanish from Task Manager. In this scenario Excel is running `out-of-process` to my WinForms app and still managed to shutdown when expected, even with double dot –  Mar 16 '15 at 23:56
  • _"I ended up down this rabbit hole after Excel started crashing on me."_ - ensure you are not using any of the evil methods in `Marshal` such as `Marshal.ReleaseComObject` as they can cause a program to fault. Such methods require great understanding of COM as well as how the COM server behaves. e.g. maybe the object is a _singleton_. Just set .NET objects to `null` and wait for the next GC or force it. –  Mar 17 '15 at 00:24

3 Answers3

7

There is very detrimental cargo cult behind that "two dot rule" silliness, it completely fails to keep C# programmers out of trouble since version 4. And it endlessly more painful than the simple way to make Office programs quit on demand.

But this is not a problem you have at all, the cargo cult only applies to an out-of-process program that use Automation to activate an Office program. Your code in fact runs inside the Office program, you of course don't care when the program terminates. Because that terminates your code as well.

Just write your code the way you'd write regular C# code, the GC does not need any help.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 3
    The `Marshal.ReleaseComObject` and 'two-dot' misunderstandings have gotten completely out of hand. Please keep spreading that message: The .NET GC works fine with COM. – Govert Mar 16 '15 at 19:48
  • Next we will be seeing the cultists complain about temporary iterator objects that are created in `foreach` loops and will want them banned. Sigh. Another fine post Hans! –  Mar 16 '15 at 23:48
  • @MickyDuncan, don't paint it as black&white, there is a certain time and place where the amount of unreclaimed RCWs after an iteration can undesireably keep an external object alive for too much time, e.g. the amount of open file handles or USER and GDI objects, or when participating in a legacy COM+ transaction, where a call to `Release` without a previous call to `SetComplete` will still commit and you want that to happen fast, etc. – acelent Mar 17 '15 at 01:57
  • Cargo cult can go both ways. Just use the necessary tools, analyse, think for yourself, don't take any general opinion without understanding it and questioning it. If forcing GC has always worked for you, can't you at least try to imagine real cases where it wouldn't, or where the GC overhead would become noticeable and probably unbearably high (e.g. heavy duty server processing)? – acelent Mar 17 '15 at 02:03
  • 1
    @PauloMadeira _"there is a certain time and place where the amount of unreclaimed RCWs after an iteration can undesireably keep an external object alive for too much time"_ - So? Use GC. Why use a potentially dangerous API method such as `Marshal.ReleaseComObject` which is code smell for an unmatched COM `AddRef/Release` when GC can be used just as effectively and without risk. –  Mar 17 '15 at 02:20
  • @MickyDuncan, "So? Use GC." I understand your point, and I *generally* agree, but you disregarded the GC overhead when forced too often. You might not notice it in an interactive application, but you will certainly feel the pain in a server application. So, *in general*, waiting for or forcing the GC is the correct approach, you should only resort to other means if you **really**, demonstrably have to. – acelent Mar 17 '15 at 09:53
  • @PauloMadeira If you are allocating that many COM objects and releasing them so frequently with GC so as to be noticeable, I would recommend you redesign your app. –  Mar 17 '15 at 15:02
  • @MickyDuncan, I'm not, but if you insert those calls to `GC.Collect()` and Gc.WaitForPendingFinalizers()` arbitrarily, you surely must do something about it, e.g. a limiter or a GC trigger request bound to a minimal interval. Note that this is all about dealing with COM objects, a situation you don't always control or where you must deal with legacy. Often, there's not much room for redesign, and that's why I say you should know when something seemingly bad may actually be a solution **in a particular case**. Redesigning may mean "do it all over again in .NET", but that's another story. – acelent Mar 17 '15 at 18:00
1

am I correct that the above code is not the way to go in VSTO?

Yes, you are on the right avenue. You need to declare different objects to release them later. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Office object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object.

You can read more about that in the Systematically Releasing Objects article. It is related to Outlook, but the same principles can be applied to all Office applications.

BTW: it doesn't depend on VSTO. It comes from the COM world...

Eugene Astafiev
  • 47,483
  • 3
  • 24
  • 45
  • _"It comes from the COM world"_ - Incorrect, it is a .NET issue where orphaned .NET objects pending GC are holding a reference to an unmanaged resource - _a COM object_. We c++ COM programmers never had any issue with so called "double-dot" - in-process or out-of-process otherwise. It is not necessary to call `ReleaseComObject` explicitly. That's just another Microsoft-recommended bad practice that has lead to nothing but confusion. –  Mar 17 '15 at 00:08
  • I do not agree with you. Do you want to say that a reference counter comes from the .Net world? :) – Eugene Astafiev Mar 17 '15 at 11:38
  • No. However I would prefer you clarify your last statement in your answer as it is open to interpretation –  Mar 17 '15 at 14:55
0

As often happens with small affirmations, the double-dot rule needs further explanation. You can use it as a mnemonic.

The reason is that each new RCW you get in .NET will hold a reference to the respective COM object. So, if you have (assuming obj is an RCW and this is the first time you're getting the other objects):

obj.Property[0].MethodThatReturnsAnotherObject()
//     1     2                  3

you're getting 3 additional RCWs. As you can see, there is only 1 extra dot. Although properties are probably the most common way of obtaining other COM objects, it's not the only way.

Usually, each RCW will only release the underlying COM object when it's garbage collected, unless you use Marshal.ReleaseComObject. Only use this method if you are completely sure you're the only one using the RCW you're releasing.


To be perfectly clear about this subject:

Only use ReleaseComObject or FinalReleaseComObject if you really, demonstrably have to, and you are completely, totally sure your piece of code is the only one referring to the RCW.


<type> propObj;
try
{
    propObj = obj.Property;
    <type> propArrayObj;
    try
    {
        propArrayObj = propObj[0];
        <type> propArrayObjReturn;
        try
        {
            propArrayObjReturn = propArrayObj.MethodThatReturnsAnotherObject();
        }
        finally
        {
            if (propArrayObjReturn != null) Marshal.ReleaseComObject(propArrayObjReturn);
        }
    }
    finally
    {
        if (propArrayObj != null) Marshal.ReleaseComObject(propArrayObj);
    }
}
finally
{
    if (propObj != null) Marshal.ReleaseComObject(propObj);
}

This is tedious, a wrapper might help here:

using System;
using System.Runtime.InteropServices;
using System.Threading;

public class ComPtr<T> : IDisposable where T : class
{
    public ComPtr(T comObj)
    {
        if (comObj == null) throw new ArgumentNullException("comObj");

        if (!typeof(T).IsInterface)
        {
            throw new ArgumentException("COM type must be an interface.", "T");
        }
        // TODO: check interface attributes: ComImport or ComVisible, and Guid

        this.comObj = comObj;
    }

    private T comObj;

    public T ComObj
    {
        get
        {
            // It's not best practice to throw exceptions in getters
            // But the alternative might lead to a latent NullReferenceException
            if (comObj == null)
            {
                throw new ObjectDisposedException("ComObj");
            }

            return comObj;
        }
    }

    ~ComPtr()
    {
        Dispose(false);
    }

    // IDisposable
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        #if !RELEASECOMPTR

        // Option 1: Safe.  It might force the GC too often.
        // You can probably use a global limiter, e.g. don't force GC
        // for less than 5 seconds apart.
        if (Interlocked.Exchange(ref comObj, null) != null)
        {
             // Note: GC all generations
            GC.Collect();
            // WARNING: Wait for ALL pending finalizers
            // COM objects in other STA threads will require those threads
            // to process messages in a timely manner.
            // However, this is the only way to be sure GCed RCWs
            // actually invoked the COM object's Release.
            GC.WaitForPendingFinalizers();
        }

        #else

        // Option 2: Dangerous!  You must be sure you have no other
        // reference to the RCW (Runtime Callable Wrapper).
        T currentComObj = Interlocked.Exchange(ref comObj, null);
        if (currentComObj != null)
        {
            // Note: This might (and usually does) invalidate the RCW
            Marshal.ReleaseComObject(currentComObj);
            // WARNING: This WILL invalidate the RCW, no matter how many
            // times the object reentered the managed world.
            // However, this is the only way to be sure the RCW's
            // COM object is not referenced by our .NET instance.
            //Marshal.FinalReleaseComObject(currentComObj);
        }

        #endif
    }
}

This would make the previous example a bit friendlier:

using (var prop = new ComObj<type>(obj.Property))
{
    using (var propArray = new ComObj<type>(prop.ComObj[0]))
    {
        using (var propArrayReturn = new ComPtr<type>(propArray.ComObj.MethodThatReturnsAnotherObject()))
        {
        }
    }
}

To avoid the ComObj property, you could implement a proxy, but I'll leave that as an exercise. Specifically, make an efficient proxy generation instead of forwarding by reflection.

acelent
  • 7,965
  • 21
  • 39
  • Unless you are expert in COM and you should avoid calling `Marshal.ReleaseComObject`, instead just set objects to `null` and wait for the next GC. _[If you are not careful you can cause the COM server to crash](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject(v=vs.100).aspx)_ –  Mar 17 '15 at 00:15
  • You don't have to be a COM expert, but I agree you should be **really** careful about releasing RCWs, I pointed that out. But in some very specific cases, you can achieve early COM object release without provoking a GC, which you can't tell if it's global or not. Now, for an addin or any hosted environment, or when you'll access and keep references to an uncontrollable set of COM objects, or even the slightest doubt whether you're calling a method directly or a property's default dispatch action, just don't use this technique. – acelent Mar 17 '15 at 01:12
  • It's a _best practice_ like not using `Task.Factory.StartNew` everywhere. Now you have to make sure your code does not accidentally call `Marshal.ReleaseComObject` **twice** or that the object is not a singleton. Why go down that _alley of potential risk_? –  Mar 17 '15 at 02:33
  • Actually, `ReleaseComObject` may be invoked as many times as you want. But I *generally* agree with you, this is a tool to be used **with care**, and if it's **really**, demonstrably needed. I updated my answer to include an example that forces GC after nulling the reference to the RCW. – acelent Mar 17 '15 at 10:11