Getting Outlook to shut down

I’ve had a number of people ask me how to get Outlook to shutdown properly when you create a managed add-in for it. I alluded to this issue briefly in my last blog. The OnDisconnection method you implement in your add-in’s implementation of IDTExtensibility2 doesn’t get called if you have outstanding variables that are still holding Outlook objects.

The secret to getting OnDisconnect called and your add-in unloaded is to listen to Explorer & Inspector close events, and when the last Explorer or Inspector has closed, you make sure you set all the variables that are holding Outlook objects to null. I also force a garbage collection after setting the variables to null to ensure that my add-in isn’t holding onto Outlook objects because of objects still waiting to be garbage collected.

Here is a little helper class that I wrote that you can create from your main Connect class in an add-in. I have the class take as a parameter an Outlook.Application object as well as a delegate to a shutdown method that you would declare in your Connect class. The method you declare in your Connect class would set all the variables that are holding Outlook objects to null similar to what this helper class does in its “HandleShutdown” method.

You might also notice that I am holding on to Explorers, Inspectors, as well as an array of Explorer or Inspector objects. I have to hold onto these things because if I don’t, event sinks I have established on these objects won’t fire. This is another variant of the classic “Why has my button stopped working” problem.

Also notice that nowhere in this code do I use the method ReleaseComObject. I am a firm believer that you should never use ReleaseComObject unless there is a known issue in a Microsoft app (aka a bug) that requires you to use it as a workaround for said bug. If you must use ReleaseComObject, make sure you are shimmed into your own AppDomain, otherwise you will do damage to your neighbors. It doesn’t help that a lot of your neighbors—a lot of popular Outlook add-ins—are loading in default domain and haven’t been shimmed.

#region Using directives

 

using System;
using Outlook = Microsoft.Office.Interop.Outlook;

 

#endregion

 

Namespace MyAddin
{
public class EventListener
{
public delegate void Shutdown();

 

  private Outlook.Application application;
private Outlook.Explorers explorers;
private Outlook.Inspectors inspectors;
private System.Collections.ArrayList eventSinks;
private Shutdown shutdownHandlerDelegate;

 

  public EventListener(Outlook.Application application, Shutdown shutdownHandlerDelegate)
{
this.application = application;
this.shutdownHandlerDelegate = shutdownHandlerDelegate;
explorers = application.Explorers;
inspectors = application.Inspectors;
eventSinks = new System.Collections.ArrayList();

 

   explorers.NewExplorer += new Outlook.ExplorersEvents_NewExplorerEventHandler(Explorers_NewExplorer);
inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);

 

   foreach (Outlook.Explorer e in application.Explorers)
{
Explorers_NewExplorer(e);
}

 

   foreach (Outlook.Inspector i in application.Inspectors)
{
Inspectors_NewInspector(i);
}
}

 

  public void Explorers_NewExplorer(Outlook.Explorer explorer)
{
eventSinks.Add(explorer);
Outlook.ExplorerEvents_Event explorerEvents = (Outlook.ExplorerEvents_Event)explorer;
explorerEvents.Close += new Outlook.ExplorerEvents_CloseEventHandler(Explorer_Close);
}

 

  public void Inspectors_NewInspector(Outlook.Inspector inspector)
{
eventSinks.Add(inspector);
Outlook.InspectorEvents_Event inspectorEvents = (Outlook.InspectorEvents_Event)inspector;
inspectorEvents.Close += new Outlook.InspectorEvents_CloseEventHandler(Inspector_Close);
}

 

  public void Explorer_Close()
{
if (application.Explorers.Count <= 1 && application.Inspectors.Count == 0)
{
HandleShutdown();
}
}

 

  public void Inspector_Close()
{
if (application.Explorers.Count == 0 && application.Inspectors.Count <= 1)
{
HandleShutdown();
}
}

 

  void HandleShutdown()
{
// Release any outlook objects this class is holding
application = null;
explorers = null;
inspectors = null;
eventSinks.Clear();
eventSinks = null;

 

   // Force a garbage collection
GC.Collect();
GC.WaitForPendingFinalizers();

 

   // call client provided shutdown handler delegate
shutdownHandlerDelegate();
}

 }
}