OOM.NET - Like a good standup comic – use scope and have good timing…

Matt Stehle used to be on my team prior and had blogged on the usage of Outlook Object Model (OOM) under .NET. However, his blog is being closed and I am re-blogging his content here.

Recently, I was helping someone with a Outlook item leak type issue involving a Task FormRegion.  The symptom was that after opening a task, closing it, and reopening the item they were getting the infamous error message, "COM object that has been separated from its underlying RCW cannot be used."  They were familiar with some of the issues discussed here and knew to call ReleaseCOMObject() on objects as they were done with them.  However, that is the only part of proper Outlook coding with .NET – you need to use scope and have good timing when you lay out .NET classes that handle events and use Outlook objects.  The following class is a simple example of that scope and timing…

Scope

As I pointed out in my original OOM.NET post, you need to consider the scope of the objects whose events you listen to.  The key is not to call ReleaseCOMObject() on an item while you are still listening to events from it.  In the class below, notice that _task is defined at the module level so that as long as the instance of TaskRegion is alive and listening to the Write() event _task will not go out of scope and get garbage collected by the CLR.

Timing

It seems like there is a general understanding now that Outlook objects need to be released.  Additionally, you must *always decrement the event handlers that you add* .  However, the humor in a good joke is not just the punch line but also good timing – you have to be strategic about when to call ReleaseCOMObject() and when to decrement the event handler.  Since this is a FormRegion class I'm utilizing the FormRegionShowing event to initialize _task and add the event handler and I use FormRegionClosed to remove the event handler and release _task.  In a simple item wrapper class you might use the constructor and a dispose method in the same way.  The goal is not call ReleaseCOMObject() until the events are unhooked, that way the COM object become separated from your RCW that is still trying to handle events.

NOTE – I'm also employing Patrick's fix in FormRegionInitializing for a FormRegion specific leak scenario…

 partial class TaskRegion
 {
     Outlook.TaskItem _task = null;
 
                 #region Form Region Factory
     [Microsoft.Office.Tools.Outlook.FormRegionMessageClass
 
                 (Microsoft.Office.Tools.Outlook.FormRegionMessageClassAttribute.Task)]
     [Microsoft.Office.Tools.Outlook.FormRegionName
 
                 ("ReleaseTaskRegion.TaskRegion")]
 
                 public partial class TaskRegionFactory
 
                 {
 
                 // Occurs before the form region is initialized.
         // To prevent the form region from appearing, set e.Cancel to true.
         // Use e.OutlookItem to get a reference to the current Outlook item.
 
                   private void TaskRegionFactory_FormRegionInitializing(object sender,
             Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e)
         {
 
                 Marshal.ReleaseComObject(e.OutlookItem);
         }
     }
 
                 #endregion
 
                    // Occurs before the form region is displayed.
     // Use this.OutlookItem to get a reference to the current Outlook item.
     // Use this.OutlookFormRegion to get a reference to the form region.
 
                   private void TaskRegion_FormRegionShowing(object sender, System.EventArgs e)
     {
         _task = this.OutlookItem as Outlook.TaskItem;
         _task.Write += new Outlook.ItemEvents_10_WriteEventHandler(_task_Write);
     }
 
                 private void _task_Write(ref bool Cancel)
     {
         System.Diagnostics.Debug.WriteLine("Write fired!");
     }
 
                 // Occurs when the form region is closed.
     // Use this.OutlookItem to get a reference to the current Outlook item.
     // Use this.OutlookFormRegion to get a reference to the form region.
 
                   private void TaskRegion_FormRegionClosed(object sender, System.EventArgs e)
     {
         _task.Write -= new Outlook.ItemEvents_10_WriteEventHandler(_task_Write);
         System.Runtime.InteropServices.Marshal.ReleaseComObject(_task);
     }
 }

…This post is a continuation of my efforts to document common issues I've seen when .NET programmers write solutions with Outlook's object model – be they separate executables, VSTO Add-ins, or Outlook FormRegions. To see all the posts in this series check out my posts with the OOM.NET tag…

…To check out more blog posts from Microsoft's Messaging Developer Support team which supports Outlook, Exchange, and other email-related development using Microsoft APIs check out the DevMsgTeam tag across all MSDN blogs…

PcREEHAN'S Articles:

https://blogs.msdn.com/pcreehan/archive/2008/03/13/outlook-crashes-when-using-outlook-object-model-in-multiple-threads.aspx