When ReleaseComObject is necessary in Outlook

Some might tell you that you never need to call Marhal.ReleaseComObject when writing managed code in Outlook. Well there are two very specific situations in which you must call RCO or else you will encounter problems. They are:

  1. You create toolbar buttons in new Inspector windows
  2. You use the Explorer.ActiveExplorer() method.

This is also a good point to tell you that if you are going to call RCO, you need to use a shim or you will hose other add-ins that are running in the same Domain as Outlook (the default).

You may also wan to read Eric Carter's post on Getting Outlook to shut down which offers a different approach for problem #2. I'm not sure if his approach fixes the corrupted folder list. He makes some great points in that post, and sadly in my case it is in fact necessary to call RCO (as far as I can tell).

1. Creating a Toolbar in new Inspector Windows

It's pretty common for an add-in developer that has created a toolbar to wish to have them appear in the Inspector windows. My Send to OneNote Powertoy does this, and I didn't find out about this problem till a few users reported it. Some kind folks at Microsoft told me how to work around it.

Basically, what happens is this. Whenever you create or modify a new Sticky Note in Outlook, Outlook tries to add your toolbar button to the Inspector. However, you can't do that. Outlook deals with this by presenting the following two dialogs. The first happens if you Lock your computer, then unlock it. The second happens when you quit Outlook.

Exhibit A: "Could not complete the operation. One or more parameter values are not valid."

Exhibit B: "The note will close and your changes will not be saved."

The solution to this problem is to:

  1. Handle the OnNewInspector Event
  2. Call Marshal.ReleaseComObject on the Sticky Note Inspector

Handle the OneNewInspector Event

See code below:

 public void OnStartupComplete(ref Array custom)
{
    this.ApplicationInspectors = this.ApplicationObject.Inspectors;

    try
    {
        this.ApplicationInspectors.NewInspector += 
             new InspectorsEvents_NewInspectorEventHandler(OnNewInspector);
    }
    catch (Exception e)
    {
        Debug.WriteLine(e);
    }
}

Where ApplicationInspectors is an instance of type Inspectors and ApplicationObject is an instance of Outlook.Application.

Call Marshal.ReleaseComObject on the Sticky Note Inspector

This is where you prevent the errors from happening.

 private void OnNewInspector(Inspector inspector)
{
    object item = inspector.CurrentItem;
    if (item is NoteItem)
    {
        Marshal.ReleaseComObject(inspector);
        inspector = null;
    }
    else
    {
        / ... /
    }
    if (item != null)
    { 
        Marshal.ReleaseComObject(item);
        item = null;
    }
}

Problem solved. Now on to problem two which is more complicated.

2. Handling Explorers in Outlook

It is very common for an Outlook add-in to use the Explorer object. If you are adding toolbars to the Explorer, or you are manipulating such things as messages, contacts etc, or you wish to get the selected item you need to call Explorer.ActiveExplorer(). The problem with calling this is that you can run into a situation where if you do not call RCO on the ActiveExplorer then you can cause Outlook to not persist the collapsed state of the folder list (all items will be collapsed when you restart) or even worse, Outlook won't close. I've found that Send to OneNote has both these problems. I wasn't aware of the collapse folder list bug till a few days ago, but I've known about the Outlook shutdown bug for months, but I could not explain it. The problem only seemed to happen when you had many add-ins installed. If my add-in was living on its own in Outlook I never saw any problems.

In order to call Marshal.ReleaseComObject() on the Explorer that you are using you need to do some work.

  1. Create a custom ExplorerCloseEvent class that holds on to the Explorer object
  2. Handle the OnExplorerClose event
  3. Call Marshal.ReleaseComObject on the closing Explorer

The reason that you have to do this is because by default the Explorer.Close() event does not pass in the current Explorer, so you have to write your own class with it's own event handler to do this.

Create a custom ExplorerCloseEvent class that holds on to the Explorer object

 public class OfficeExplorerCloseEvent : IDisposable
{
    private Outlook.ExplorerEvents_Event explorer;
    private Handler handler;

    public delegate void Handler(object sender, EventArgs args);

    public OfficeExplorerCloseEvent(object explorer, Handler handler)
    {
        if (explorer == null)
            throw new ArgumentNullException("explorer");
        if (handler == null)
            throw new ArgumentNullException("handler");

        this.explorer = (Outlook.ExplorerEvents_Event) explorer;
        this.handler = handler;

        HookEvent();
    }

    public object Explorer
    {
        get
        {
            return (this.explorer);
        }
    }

    public void Dispose()
    {
        this.explorer.Close -= 
            new Microsoft.Office.Interop.Outlook.ExplorerEvents_CloseEventHandler(this.ForwardExplorerEvent);
    }

    private void HookEvent()
    {
        this.explorer.Close +=
            new Microsoft.Office.Interop.Outlook.ExplorerEvents_CloseEventHandler(this.ForwardExplorerEvent);
    }

    private void ForwardExplorerEvent()
    {
        this.handler(this, new EventArgs());
    }
}

Handle the OnExplorerClose event. This code builds on the first code snippet of the OnStartupComplete() method.

 public void OnStartupComplete(ref Array custom)
{
    this.ApplicationExplorers = this.ApplicationObject.Explorers;
    this.ApplicationInspectors = this.ApplicationObject.Inspectors;

    try
    {
        this.ApplicationInspectors.NewInspector += 
             new InspectorsEvents_NewInspectorEventHandler(OnNewInspector);
    }
    catch (Exception e)
    {
        Debug.WriteLine(e);
    }
    
    if (this.ApplicationObject.Explorers.Count > 0)
    {
        this.ApplicationExplorer = this.ApplicationObject.ActiveExplorer();
        new OfficeExplorerCloseEvent(this.ApplicationObject.ActiveExplorer(), 
            new OfficeExplorerCloseEvent.Handler(this.OnExplorerClose));
    }

    Marshal.ReleaseComObject(this.ApplicationObject.ActiveExplorer());
    Marshal.ReleaseComObject(this.ApplicationExplorer);
}

As you can see in the last two lines I call RCO on the ActiveExplorer and the ApplicationExplorer (which is just an instance of the current Explorer). I'm not 100% sure if you have to do this, but I gave up debugging this nonsense after a few hours and just left it in there.

You must also be sure to see if you have any existing Explorers before doing this as you don't want to hook the event unless Outlook is starting in UI mode (it can be instantiated through ActiveSync for example w/o any UI and in that case Explorers will not exist).

Call Marshal.ReleaseComObject on the closing Explorer

Now that we have hooked the OnExplorerClose lets see what we do in that Method.

 private void OnExplorerClose(object sender, EventArgs args)
{
    Explorer explorer = ((OfficeExplorerCloseEvent) sender).Explorer as Explorer;
    ((IDisposable) sender).Dispose();


    while (true)
    {
        if (Marshal.ReleaseComObject(explorer) == 0)
        {
            break;
        }
    }


    explorer = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();
}

In the above code snippet I am getting the Explorer instance that the OfficeExplorerCloseEvent is holding, and calling RCO on it till the RefCount is 0. This ensures that the are all disposed. Then I call the GarbageCollector to clean things up for me.

Final Thoughts

I hope this shows you that doing what appears to be straightforward with managed code in Outlook isn't. I could write a few more blog posts about things I've encountered, and probably will when time permits. I'm pretty excited because for the first time in months, Outlook is shutting down cleanly 100% of the time!

I would also like to thank all those folks that helped me with this problem, or provided code to guide me. I can't actually remember who helped me get this far...