Moving Away from COM AddIns


Chapter 23, "Developing COM AddIns for Word and Excel," examined several issues with building COM add-ins in Visual Basic 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 AddIns 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 solve 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 AddIn

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 down; the outlook.exe process will continue running, waiting for the COM add-in to release the Outlook objects it is holding.

To get Outlook to shut down and call the COM add-in's OnDisconnection method, you must use a trick that involves listening to Outlook events to determine when the last window has been closed. Outlook windows are represented by two object model objects. The Explorer object in the Outlook object model represents the main Outlook window, which consists of a view showing folders and items in folders. It is possible to open additional Explorer views by rightclicking an Outlook folder and choosing Open in New Window. The Inspector object in the Outlook object model represents the Outlook window that appears when you doubleclick an individual item in a folder, such as a mail item, contact item, or other Outlook item.

The secret to getting OnDisconnection called and your COM add-in to unload is to listen to Explorer and Inspector Close events, as well as the Application object's Quit event. When the last Explorer or Inspector has closed, or when the Application object's Quit event is raised, you must make sure that you set all the variables that are holding Outlook objects to Nothing. Then you should force a garbage collection after setting the variables to Nothing to ensure that your add-in will not hold on to Outlook objects because objects are waiting to be garbagecollected.

Listing 24.1 shows a helper class that you can create and use from your main Connect class in a COM add-in. The class takes 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 Shutdown method you declare in your Connect class would set all the class member variables in the Connect class that are holding Outlook objects to Nothing, similar to what this helper class does in its HandleShutdown method. Note that you do not have to use this approach or this class in VSTO Outlook add-insonly in COM add-ins. This is one of the strong arguments for switching to VSTO Outlook add-ins.

You might also notice that the helper class holds on to the Explorers and Inspectors collection objects, as well as an array of Explorer or Inspector objects. The helper class holds on to these things because if it does not, the event sinks it has established on these objects will not work. This is another variant of the classic "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 AddIn Shut Down Properly[1]

Imports Outlook = Microsoft.Office.Interop.Outlook Public Class EventListener   Public Delegate Sub Shutdown()   Private application As Outlook.Application   Private explorers As Outlook.Explorers   Private inspectors As Outlook.Inspectors   Private eventSinks As System.Collections.ArrayList   Private shutdownHandlerDelegate As Shutdown   Private Sub EventListener( _     ByVal application As Outlook.Application, _     ByVal shutdownHandlerDelegate As Shutdown)     Me.application = application     Me.shutdownHandlerDelegate = shutdownHandlerDelegate     explorers = application.Explorers     inspectors = application.Inspectors     eventSinks = New System.Collections.ArrayList()     AddHandler explorers.NewExplorer, _       AddressOf Explorers_NewExplorer     AddHandler inspectors.NewInspector, _       AddressOf Inspectors_NewInspector     AddHandler application.Quit, AddressOf Application_Quit     Dim e As Outlook.Explorer     For Each e In application.Explorers       Explorers_NewExplorer(e)     Next     Dim i As Outlook.Inspector     For Each i In application.Inspectors       Inspectors_NewInspector(i)     Next   End Sub   Public Sub Explorers_NewExplorer( _     ByVal explorer As Outlook.Explorer)     eventSinks.Add(explorer)     Dim explorerEvents As Outlook.ExplorerEvents_Event = _       CType(explorer, Outlook.ExplorerEvents_Event)     AddHandler explorerEvents.Close, AddressOf Explorer_Close   End Sub   Public Sub Inspectors_NewInspector( _     ByVal inspector As Outlook.Inspector)     eventSinks.Add(inspector)     Dim inspectorEvents As Outlook.InspectorEvents_Event = _       CType(inspector, Outlook.InspectorEvents_Event)     AddHandler inspectorEvents.Close, AddressOf Inspector_Close   End Sub   Public Sub Explorer_Close()     If application.Explorers.Count <= 1 And _       application.Inspectors.Count = 0 Then       HandleShutdown()     End If   End Sub   Public Sub Inspector_Close()     If application.Explorers.Count = 0 And _       application.Inspectors.Count <= 1 Then       HandleShutdown()     End If   End Sub   Public Sub Application_Quit()     HandleShutdown()   End Sub   Private Sub HandleShutdown()     ' Release any Outlook objects this class is holding     application = Nothing     explorers = Nothing     inspectors = Nothing     eventSinks.Clear()     eventSinks = Nothing     ' call client provided shutdown handler delegate     shutdownHandlerDelegate()     ' Force a garbage collection     GC.Collect()     GC.WaitForPendingFinalizers()     GC.Collect()     GC.WaitForPendingFinalizers()   End Sub End Class 


[1] This class is not necessary for VSTO Outlook add-ins.

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 Nothing and forcing a garbage collection, as shown in Listing 24.1. ReleaseComObject is a function in the CLR that, if you misuse it, has some additional side effects that can adversely affect your code. It can also affect other COM add-ins if you are not using a COM add-in shim as described in Chapter 23, "Developing COM AddIns for Word and Excel." 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 Listing 24.1.

To understand ReleaseComObject, it is necessary to understand more of what is really happening when your code runs inside the Outlook process. The first thing you need to understand is the concept of an application domain, or AppDomain. An application domain is an isolated environment in which your code runs within a processin this case, within outlook.exe. You can think of an application domain as being sort of a process within a process. There can be one or more application domains running inside a single process. There are several ways that an application domain provides processlike isolation. An application domain can be stopped and unloaded without affecting another application domain. Individual application domains can be configured differently with different security policy, different settings for loading assemblies, and so on. Code running in one application domain cannot directly access code in another application domain. In addition, faults occurring in one application domain cannot affect other application domains.

With typical console applications or Windows Forms applications, you usually will have just one application domain where your code will run. There always is at least one application domain created automatically for any process running managed code. The application domain the CLR creates automatically is called the default application domain. The default application domain can be unloaded only when the process exits. This is often acceptable, because you typically control all the code that loads into a console application or Windows Forms application that you have written.

In Office scenarios, you will want to have multiple application domains created in the same process where each add-in loads into its own application domain. This is desirable because if you load in the same application domain as another add-in, that add-in can adversely affect you, as discussed shortly. You also will not want to have an add-in or customization associated with a document load into the default application domain, because the default application domain can be unloaded and cleaned up only when the process exits. A user might want to unload an add-in or close a document, and she will not want the customization to stick around in memory in the default application domain. Users will want the add-in to unload and free that memory for other uses.

Figure 24.1 shows the most desirable situation for Outlook COM add-ins (and Office COM add-ins in general). If each COM add-in is shimmed as described 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 domain on a user's machine, and chaos will ensue.

Figure 24.1. An ideal situation for add-ins. Each add-in loads in its own application domain. No add-ins load into the default application domain.


If you do not use a shim to load a COM add-in and instead let mscoree.dll load your add-in, you will end up with a situation such as the one shown in Figure 24.2. COM add-ins that are not shimmed are loaded into the default application domain by default.

Given Figure 24.1 and Figure 24.2, now we consider what happens when you use a COM object in your customization. When you use a COM object in your customization, such as Outlook's Application object, the CLR creates an object called a Runtime Callable Wrapper (RCW) for the COM object that your managed code talks to. The RCW in turn talks to the actual COM object. Any time your code talks to Outlook's Application object, your code is actually talking through the RCW.

Figure 24.2. The undesirable situation that occurs when add-ins are not shimmed and loaded by mscoree.dll.


RCWs are scoped to an application domain. The CLR creates one RCW that all code in a given application domain will use to talk to Outlook's Application object. Figure 24.3 shows the ideal situation for RCWs. With each add-in loaded into its own application domain, each add-in has its own RCWs. Figure 24.3 also illustrates that when multiple variables are declared in a particular application domain that are set to an instance of Outlook's Application object, they share an RCW object. Note that the RCW is shared because Outlook's Application object is a singleton COM object. For nonsingleton COM objects, the RCW is not shared, and the situation described below does not have as great an impact.

Figure 24.3. An ideal situation for add-ins. Addins should not share RCWs.


Now we consider what ReleaseComObject does. Suppose that you have a class variable in your add-in code called appObject1 that is set to an instance of Outlook's Application object. You might have another class variable in another area of your add-in called appObject2 that is also set to an instance of Outlook's Application object. Even though you have two variables set to an instance of Outlook's Application object, these two variables will both share one RCW that is at the application domain level.

Now suppose that appObject1 gets set to an instance of Outlook's Application object first. This causes Outlook's Application RCW to be created. The RCW is referencecountedthat is, a count is kept of each variable that is using the RCW. So the reference count of the RCW goes to 1. In addition, the RCW talks to the COM object for Outlook's Application object and adds a reference count to the COM object, too. Now Outlook knows that some code is "using" one of its objects. Later in the code, appObject2 gets set to an instance of Outlook's Application object. The CLR detects that an RCW is already available, so it increments the reference count on the RCW and has appObject2 share the RCW with appObject1. It does not increment the reference count on the COM object, however; the RCW will take only one reference count on the COM object, and it will release that reference count when all the variables using the RCW are garbagecollected.

Because Outlook is more strict about reference counts than the other Office applications are, to get Outlook to shut down, you need to release the reference count the RCW has made on any COM objects your managed code is using when the last Outlook window (either Explorer or Inspector) is closed or when Outlook's Application object raises the Quit event. The right way to do this is to set all the variables you have set to Outlook objects to Nothing and then force two garbage collections. The quickanddirty way to do this is to use ReleaseComObject. When you call ReleaseComObject on a variable, the CLR releases the reference count on the RCW associated with that variable type. So if you want to get rid of the RCW for Outlook's Application object and thereby release Outlook's COM object to get it to shut down properly, you could write the following code:

Runtime.InteropServices.Marshal.ReleaseComObject(appObject1) Runtime.InteropServices.Marshal.ReleaseComObject(appObject2) 


Note that this assumes that only two variables in the application domain 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's 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 evenscarier code:

Dim count as Integer Do   count = Runtime.InteropServices.Marshal.ReleaseComObject( _     appObject1) Loop While count > 0 


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. .NET also provides another method that is the equivalent of calling ReleaseComObject in a loop. This method is shown here:

Runtime.InteropServices.Marshal.FinalReleaseComObject(appObject1) 


After the RCW has gone away because of calling ReleaseComObject, ReleaseComObject in a loop, or FinalReleaseComObject, if 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 box shown in Figure 24.4.

Figure 24.4. The error that occurs when you try to talk to a variable whose RCW has been released.


So you probably can see that if you load into your own application domain, if you are not using any referenced libraries that talk to Outlook's application object, and if you can avoid talking to any properties or methods of Outlook's Application object after you have called ReleaseComObject in a loop or FinalReleaseComObjectyou could get away with using this approach. This is only because you are in your own application domain and presumably are in control of all the code that might load there. If you shoot anyone in the foot by using ReleaseComObject, it will be yourself and not other developers.

Consider what happens if you are not using a shim, and you load into the default application domain. Now you have great potential to affect adversely other add-ins that also are not shimmed and are loading into the default application domain. Figure 24.5 shows this situation. Suppose that Addin 1 calls FinalReleaseComObject on its appObject1 object. This will not only release the references that Addin 1 has on the RCW, but because the RCW is shared at the application domain level and Addin 2 is also loaded in the same application domain, it will also release the references that Addin 2 has on the RCW. Now, even if Addin 1 is smart enough not to touch appObject1 anymore, Addin 2 has no way of knowing that when it talks to appObject2 or appObject3, it will get an exception due to the RCW's going away.

Figure 24.5. Worstcase situation for add-ins: Addins share RCWs, and one add-in calls ReleaseComObject in a loop or FinalReleaseComObject.


If, instead, Addin 1 sets appObject1 to Nothing and forces a garbage collection, .NET will make sure that the right number of reference counts is released on the RCW without affecting other users of the RCW. Also, with appObject1 set to Nothing, it will be clearer in your code that you are no longer allowed to talk to appObject1.

The CLR does not clean up the reference counts on the RCW until the variable you have set to Nothing is garbagecollected. In Listing 24.1 earlier in the chapter, where we are trying to clean up the reference count immediately after the last window is closed, we force a garbage collection immediately after setting the variables referring to Outlook objects to Nothing. To force the garbage collection, we call GC.Collect(), followed by GC.WaitForPendingFinalizers(). Note that then we call GC.Collect() and GC.WaitForPendingFinalizers() a second time to ensure that any RCWs that were stored as members of objects with finalizers are cleaned up properly.

How Outlook AddIn Development Should Be: The VSTO Outlook AddIn 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 Nothing 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. Also, this situation never occurs in VSTO 2005 customizations because of VSTO's better model for loading and unloading code.

If you use the VSTO Outlook add-in project, you will not have to worry about any of these Outlookspecific shutdown problems or any of the problems that we said (in Chapter 23, "Developing COM AddIns for Word and Excel") require a shim. 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 Nothing 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 Nothing, RCWs, or any of the complexity discussed in this section.




Visual Studio Tools for Office(c) Using Visual Basic 2005 with Excel, Word, Outlook, and InfoPath
Visual Studio Tools for Office: Using Visual Basic 2005 with Excel, Word, Outlook, and InfoPath
ISBN: 0321411757
EAN: 2147483647
Year: N/A
Pages: 221

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net