Andrei Smolin

Why Outlook is not closing when I run my add-in?

Here is a short answer: you should release all references to Outlook objects before your add-in is closed.

For COM developers (VB 6) it means that you should set all your variables referring to Outlook objects to Nothing (it’s a VB equivalent of Null, null or nil that are used in other programming languages, C#, C++, J#, Delphi).
For .NET developers it means that you should call System.Runtime.InteropServices.Marshal.ReleaseComObject for all of your variables that refer to Outlook objects.

What follows below is a detailed answer.

COM and .NET are two approaches to the problem of code reuse. If you think of them as of two slightly different sorts of magic, you are right, because normally you needn’t know their details. But if you use both of them simultaneously, then you should be aware of their differences that can make your life as hard as it can be.

The creators of COM and .NET had taken two absolutely different decisions on one of the crucial problems: memory cleaning.

In COM, they decided to kill a COM object immediately when it is not referred to by any other objects. To fulfill this task, they added to COM the reference count mechanism: every time you create a COM object, you must increase the reference counter for this object by one, and every time you release a COM object, you must decrease the reference counter for this object by one. When the reference counter becomes zero, the COM library frees the memory allocated to this object. Fortunately, VB6 makes this trick behind the scene and you may even don’t know about it.

In .NET, they decided to kill a .NET object with the use of the Garbage Collector (GC). It runs periodically and unloads .NET objects not referenced any more. Sure, .NET also uses some reference counting mechanism but it is out of scope of this article.

Consider the following sample code in VB6, which is a COM-based language. Note also, Outlook (as well as other Office applications) is built on the COM technology.

Sample 1
Sub Main()
Dim olApp as Outlook.Application
Set olApp = GetObject(, "Outlook.Application")
Set olApp = Nothing
End Sub

The Dim operator is transformed by the compiler to the memory allocation for the olApp variable (4 bytes). It also says the compiler which class type this variable contains.

The first Set operator is transformed by the compiler to the following sequence of actions:

1. Initialize the COM Library.
2. Make the COM Library find an existing COM object of the “Outlook.Application” type.
3. If the COM object is not of the same type as the olApp variable, then fire the “Type mismatch” error (exception). You can replace Dim olApp as Outlook.Application with Dim olApp as Excel.Application, and you will get the “Type mismatch” error in this Set operator.
4. If they are of the same type, then increase the reference counter for the COM object by one and write the address of the COM object to the memory allocated for olApp.

The last Set operator is transformed to the following actions:

1. Decrease the reference counter by one for the COM object whose address is kept in the memory allocated for olApp.
2. Write Nothing to the memory allocated for olApp.

You see that the last set of actions doesn’t really free the COM object. Instead, the COM Library is responsible for this part of the game: whenever the reference counter is decreased it checks the counter value and when the value becomes zero, the COM Library frees the COM object. In our sample this means that the Outlook application object created in this procedure will be freed.

You can safely omit the last line because the Visual Basic 6 compiler adds to every procedure a set of commands to free all local variables, and if a local variable is of any COM type, the compiler adds some code to decrease the reference counter for the variable.

Add-ins differ from any other program in the startup / finish conditions. Add-ins are started and finished through the special methods of the IDTExtensibility2 interface. That means that it is the add-in developer who is responsible for cleaning up the add-in’s variables. If by mistake you don’t release a single COM variable, (in VB terms, set it to Nothing), then there is nobody to clean it and the host application hangs. Please read the following note before proceeding.

NOTE. We don’t recommend debugging your add-ins against your actual Outlook data file, because there is a probability of crashing it. Use the Mail applet in the Control Panel to create a test profile.
Use it for debugging your add-in. Having finished with debugging, switch your add-in off using the COM Add-ins dialog.

The following code module (in VB6) effectively makes Outlook hang.

Sample 2
Option Explicit
Dim mExplorer As Object

Private Sub AddinInstance_OnAddInsUpdate(custom() As Variant)

End Sub

Private Sub AddinInstance_OnBeginShutdown(custom() As Variant)

End Sub

Private Sub AddinInstance_OnConnection(ByVal Application As Object, _
ByVal ConnectMode As AddInDesignerObjects.ext_ConnectMode, _
ByVal AddInInst As Object, custom() As Variant)
Set mExplorer = Application.ActiveExplorer
End Sub

Private Sub AddinInstance_OnDisconnection(ByVal RemoveMode _
As AddInDesignerObjects.ext_DisconnectMode, custom() As Variant)
End Sub

Private Sub AddinInstance_OnStartupComplete(custom() As Variant)

End Sub

In this code, the mExplorer variable is created in the OnConnection method and isn’t freed in either OnDisconnection or OnBeginShutdown methods, which are provided by the IDTExtensibility2 interface for cleaning the add-in variables referencing COM objects.

So, Outlook hangs if you don’t release COM variables.

NOTE. Outlook 2003 includes a code that prevents such hanging. Nevertheless, it takes several seconds for Outlook to understand that the add-in doesn’t release references to Outlook objects and Outlook kills the add-in.

In .NET the add-in built on the sample 2 schema will hang Outlook for exactly the same reason. But .NET provides another way to hang Outlook.

Consider the following code fragment in VB 6.

.......
Dim CurrentMail as Outlook.MailItem
Set CurrentMail = olApp.ActiveExplorer.Selection.Item(1)
.......

This fragment includes the creation of several COM objects: the ActiveAxplorer property returns a COM object of the Outlook.Explorer type, Selection returns Outlook.Selection and Item(1) method returns a mail item. Every object is created with increasing the reference counter. The Explorer and Selection objects are freed after being used with decreasing their reference counters. All the reference counter jugglery is made by the code inserted by the VB6 compiler.

Now consider the same code fragment transferred to VB.NET.

...
Dim CurrentMail As MailItem = olApp.ActiveExplorer.Selection.Item(1)
...

The same COM objects will be created in .NET at run-time. In .NET they will be created as part of .NET wrapper classes (runtime callable wrapper – RCW) that increase reference counters and contain references to appropriate COM objects. But .NET doesn’t do anything to clear COM objects shifting off the responsibility to the developer. If you don’t change the code above, then due to the fact that neither .NET itself nor .NET wrapper classes free COM objects, these .NET wrappers will be unloaded from the memory when the garbage collection starts. And Outlook hangs again. Oh, of course Outlook will unload when .NET wrappers will be unloaded, but it stays in memory until the garbage collection starts. Plus, Outlook 2000 will hang forever because its creators didn’t know about .NET.

So what to do? The answer is the ReleaseComObject method included in the .NET Framework for solving such problems. It is located in the System.Runtime.InteropServices.Marshal class. With the use of this method the code fragment should be rewritten as follows

...
Imports Microsoft.Office.Interop.Outlook
Imports System.Runtime.InteropServices
...
Dim olExplorer As Explorer = Nothing
Dim olSelection As Selection = Nothing
Dim CurrentMail As MailItem = Nothing
Try
olExplorer = olApp.ActiveExplorer
olSelection = olExplorer.Selection
CurrentMail = olSelection.Item(1)
Catch Ex As System.Exception
'
Finally
If olExplorer IsNot Nothing Then Marshal.ReleaseComObject(olExplorer)
If olSelection IsNot Nothing Then Marshal.ReleaseComObject(olSelection)
If CurrentMail IsNot Nothing Then Marshal.ReleaseComObject(CurrentMail)
End Try
...

Thus, .NET programming for Office (and particularly for Outlook) creates another source of bugs because one may forget to release COM object variables.

This is one more reason to use Add-in Express .NET, which takes upon itself a large part of Office programming creating toolbars that work in multi-Explorer and multi-Inspector modes, Option pages and Folder Property pages, intercepting keyboard shortcuts and Outlook events.

Post a comment

Have any questions? Ask us right now!