Moving Away from COM Add-Ins
Chapter 23, "Developing COM Add-Ins for Word and Excel," examined several issues with building COM add-ins in C# for Office applications. In particular, the chapter considered several problems with using the default configuration of a COM add-in where mscoree.dll loads the COM add-in:
- Mscoree.dll can be disabled, causing all managed COM add-ins to stop loading.
- Mscoree.dll cannot be signed, which makes it so your COM add-in cannot be loaded when the Trust all installed add-ins and templates option is not checked.
- Mscoree.dll loads all COM add-ins into the same application domain, which allows COM add-ins to affect one another adversely.
VSTO add-ins for Outlook solves these issues. VSTO also fixes some other issues in Outlook COM add-in development that we consider here to motivate you to use the VSTO add-in technology rather than the COM add-in technology described in Chapter 23. This chapter describes the problems with the COM add-in technology in enough detail so that if you are forced to use a COM add-in approach, you will know how to work around these issues.
Getting Outlook to Shut Down Properly with a COM Add-In
The most troublesome issue in Outlook COM add-in development is that the OnDisconnection method you implement in a COM add-in sometimes is not called if you have variables such as a class member variable that is holding an Outlook object. The result is that when Outlook exits, all the Outlook windows go away but Outlook does not shut downthe outlook.exe process will continue running, waiting for the COM add-in to release the Outlook objects it is holding.Why has my button stopped working" problem described in Chapter 1, "An Introduction to Office Programming."
Listing 24-1. A Helper Class That Helps an Outlook COM Add-In Shut Down Properly (This class is not necessary for VSTO Outlook add-ins.)
using System;
using Outlook = Microsoft.Office.Interop.Outlook;
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);
((Outlook.ApplicationEvents_10_Event)application).Quit +=
new Outlook.ApplicationEvents_10_QuitEventHandler(
Application_Quit);
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();
}
}
public void Application_Quit()
{
HandleShutdown();
}
void HandleShutdown()
{
// Release any outlook objects this class is holding
application = null;
explorers = null;
inspectors = null;
eventSinks.Clear();
eventSinks = null;
// call client provided shutdown handler delegate
shutdownHandlerDelegate();
// Force a garbage collection
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
Understanding RCWs, Application Domains, and Why to Avoid Calling ReleaseComObject
Some Outlook developers have used ReleaseComObject on class member variables holding Outlook objects instead of setting these variables to null and forcing a garbage collection as shown in Chapter 23. For this reason, we recommend against using ReleaseComObject. Because it has been recommended in the past, it is important to describe in more detail why calling ReleaseComObject is not advised. This will eventually lead us to VSTO Outlook add-ins and a description of why they do not have to do any of the tricks shown in Chapter 23, each add-in will load into its own application domain, providing isolation so that one add-in cannot affect another. Note that no add-ins load into the default application domain in Figure 24-1. If you do not shim a COM add-in, mscoree.dll will load it into the default application domain. If we could rule the add-in world, no add-ins would ever load into the default application domain. You should avoid loading into the default application domain because a tested COM add-in that works fine on your developer machine might conflict with some other add-in loading into the default application machine on a user's machine and chaos will ensue.
Figure 24-1. An ideal situation for add-inseach add-in loads in its own application domain. No add-ins load into the default application domain.

Figure 24-2. The undesirable situation that occurs when add-insare not shimmed and loaded by mscoree.dll.

Figure 24-3. An ideal situation for add-insadd-ins should not share RCWs.

Note that this assumes that there are only two variables in the application domain that are using the RCW: appObject1 and appObject2. If you forgot about a variable that was set to Outlook's Application object or you are referencing a library that sets its own internal variables to Outlook's Application object, this code would not result in the RCW going away and releasing Outlook's COM object because the reference count on the RCW would be greater than 2.ReleaseComObject also returns the number of reference counts left on the RCW. So armed with this knowledge, you could write this even scarier code:
Runtime.InteropServices.Marshal.ReleaseComObject(appObject1);
Runtime.InteropServices.Marshal.ReleaseComObject(appObject2);
This code keeps releasing the reference count on the RCW until it goes to 0, which then causes the RCW to be released and the COM object it is talking with to have its reference count released. This code would get rid of the RCW even in the case where you forgot about a variable that was set to Outlook's Application object or using a library that was using Outlook's Application object. The CLR also provides another method that is the equivalent to calling ReleaseComObject in a loop. This method is shown here:
int count;
do
{
count = Runtime.InteropServices.Marshal.ReleaseComObject(appObject1);
}
while (count > 0);
After the RCW has gone away because of calling ReleaseComObject, ReleaseComObject in a loop, or FinalReleaseComObjectif you attempt to use any of the properties or methods on any variables that were set to the Outlook Application object (for example you try to access appObject1.Name)you will get the error dialog shown in Figure 24-4.
Runtime.InteropServices.Marshal.FinalReleaseComObject(appObject1);
Figure 24-4. The error that occurs when you try to talk to a variable whose RCW has been released.

Figure 24-5. Worst-case situation for add-insadd-ins share RCWs and one add-in calls ReleaseComObject in a loop or FinalReleaseComObject.

How Outlook Add-In Development Should Bethe VSTO Outlook Add-In Project
Outlook COM add-in development requires you to track any variables set to Outlook objects, sink the Close events of the Inspector and Explorer objects, set your variables set to Outlook objects to null when the last Inspector or Explorer closes or the Application object's Quit event is raised, and force two garbage collections. This complexity is not required when building add-ins for other Office applications, so do not apply these techniques to Excel or Word. Excel and Word are more robust to reference counts on their COM objects being held during the shutting down of the application. This situation also never occurs in VSTO 2005 customizations because of VSTO's better model for loading and unloading code.Chapter 23. The VSTO Outlook add-in project uses the VSTO model for loading and unloading an add-in. The VSTO model always loads a customization into its own application domain. When the add-in is unloaded or the application exits, VSTO raises a Shutdown event into the customization. The developer does not have to set any objects to null or force a garbage collection to clean up RCWs because once the Shutdown event handler has been run, VSTO unloads the application domain associated with the customization. When the application domain is unloaded, all the RCWs used by that application domain and customization are cleaned up automatically and the references on COM objects are released appropriately. After the application domain has been unloaded, memory used by the customization is freed, and the process can continue to run. Because VSTO Outlook add-ins apply this approach to add-ins, you never have to worry about setting variables to null, RCWs, or any of the complexity discussed in this section.