Ask Learn
Preview
Please sign in to use this experience.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.
Randy Byrne
Micro Eye, Inc.
October 2002
Applies to:
Microsoft® Visual Basic® .NET
Microsoft Outlook® 2002
Summary: Learn how to use Visual Basic .NET to develop COM add-ins for Outlook 2002. The sample Visual Basic .NET Outlook COM add-in resolves Contact mailing addresses with an XML Web service. (28 printed pages)
Download odc_oladdinvbnet.exe.
Introduction
Support for Add-ins in Visual Studio .NET
Using the Office XP Primary Interop Assembly
The OutlookCOMAddinVBNet Sample Application
Coding Fundamentals for a Visual Basic. NET Outlook COM Add-in
Working with a Setup Project
Conclusion
Add-ins let developers extend Microsoft® Outlook® 2002 beyond its native capabilities. For Microsoft Office developers, add-ins are commonly known as COM add-ins since they depend on the Component Object Model (COM) to operate in-process with Outlook. You might wonder how a managed code assembly can interoperate with Outlook, which remains unmanaged code dependent on COM. The solution is to install the Microsoft Office XP Primary Interop Assemblies (PIAs) to ensure interoperability. When you deploy your setup package, you should also package the relevant PIA. You will learn how to code an Outlook add-in using Microsoft Visual Basic® .NET. The Visual Basic .NET sample add-in illustrates best-practice coding techniques that you can use in your own solutions. The sample application is a well-behaved Outlook COM add-in that consumes an XML Web service. For non-commercial, private use only, this XML Web service allows you to validate ContactItem mailing addresses against the U.S. Postal Service database.
It's assumed that the reader:
Visual Studio .NET has built-in support for Office add-ins. You can create an Office add-in by following these steps:
The Visual Studio Add-in Wizard, however, omits several blocks of code that are essential for an add-in to work properly in Outlook 2002. At present, there are also problems that relate to the interop assembly generated by Visual Studio .NET when you set a reference to the Outlook Object Library. An interop assembly contains metadata that helps your managed code to communicate with a COM-based type library, such as the Microsoft Outlook 10.0 Object Library. The interop assembly created by Visual Studio .NET is known as a TLBIMP-generated interop assembly. TLBIMP is invoked when you add a reference to your Visual Basic .NET project and select object libraries from the COM page of the References dialog box shown in Figure 1. For example, let's assume that you choose to add a reference to the Microsoft Outlook 10.0 Object Library. Once you add a reference to the Microsoft Outlook 10.0 Object Library to your project, a file named Outlook.Interop.dll will be created in the bin folder of your project.
Figure 1. The Add Reference dialog box
When you use the Visual Studio Add-in Wizard to create an Outlook add-in, and you have not installed the Office XP Primary Interop Assembly, you will discover the following:
For Each objMessage In colMessages
.For additional details, and for instructions on fixing the TLBIMP-generated interop assembly, see the following Microsoft Knowledge Base articles:
To solve some of the problems you might experience with a TLBIMP-generated Interop Assembly, download the Office XP Primary Interop Assemblies (PIAs). When you install the Office XP Primary Interop Assemblies, you no longer have to fix the TLBIMP-generated interop assembly. Instead of creating a separate interop assembly in the bin folder for each project that references an Office XP type library, the primary interop assembly resides in the global assembly cache (GAC). Think of a PIA as an Interop Assembly that is given a special status because the PIA has been digitally signed by the publisher of the original COM component. In this case, the publisher of the Outlook Object Library is Microsoft. Figure 2 shows the Microsoft.Office.Interop.Outlook PIA as it appears in the GAC.
Figure 2. The global assembly cache
Before you run the sample code that accompanies this article, you must install the Office XP PIAs on your development computer. If you want to distribute your solution in a setup package, you must add Microsoft.Office.Interop.Outlook.dll to the setup project.
Extract the Office XP PIAs to a directory on a computer with Visual Studio .NET installed.
Click (All) Programs on the Start menu, point to Microsoft Visual Studio .NET, point to Visual Studio .NET Tools, and click Visual Studio .NET Command Prompt. The Visual Studio .NET Command Prompt window appears. This facilitates the use of the .NET Framework SDK tools.
In the Visual Studio .NET Command Prompt window, use the cd (change directory) command to change to the file directory in which the Office XP PIAs were extracted. For example, if you extracted the Office XP PIAs to a C:\Office XP PIAs\ folder, type
cd C:\Office XP PIAs\
and press ENTER.
To install all of the Office XP PIAs at the same time, type
register.bat
and press ENTER.
Assuming that you have already built your Outlook add-in project and added a Setup project to your solution, add the Outlook 2002 PIA to the Global Assembly Cache output folder. The PIAs and .reg files that ship with the Office XP Primary Interop Assembly are redistributable.
Next, add the Outlook 2002 PIA registry entries to the Registry on Target Machine pane.
The sample application gives you a working example of an Outlook COM Add-in created with Visual Basic .NET. Before you start to look at the sample code, you need to download, install and then test the Outlook COM Add-in for VB.NET.
The OutlookCOMAddinVBNET folder contains the sample managed COM add-in Microsoft Visual Basic .NET project. The name of the COM add-in assembly is OutlookCOMAddinVBNET. This project will be used to demonstrate how to code an Outlook COM add-in using Visual Basic. NET.
Note Before installing the managed OutlookCOMAddinVBNET COM add-in DLL, close Microsoft Outlook.
To install the COM add-in, do the following:
After you have installed the sample COM add-in, launch Outlook. You should see a Visual Basic .NET command bar button on the Standard toolbar of the Outlook Explorer Window. The Explorer command bar button displays an About Outlook COM Add-in for VB.NET dialog box. Provided that you have a working Internet connection, you will also be able to resolve postal addresses for Contact items in a Contacts folder. The add-in creates a Resolve Zip command button (shown in Figure 3). When you click the Resolve Zip button for an Outlook Contact item, the postal address is resolved against a U.S. Postal Service database by an XML Web service. Finally, the add-in prevents you from sending messages with a blank subject. Coding this functionality into an Outlook COM add-in is explained below.
Figure 3. Resolve Zip command bar button
The following sections discuss many of the coding fundamentals that are recommended when you create an add-in for Outlook. Some of these elements are required while others are optional and reflect the preference of the author. Although not all the code in the sample application will be discussed in this article, there are some code elements that are essential to building a well-behaved Outlook add-in. For example, you must implement the Extensibility.IDTExtensibility2 interface in your add-in Connect class. If you don't implement this interface, then Outlook will not be able to load your managed code add-in. You also must use coding techniques that are specific to Outlook to ensure that your add-in does not cause Outlook.exe to remain in memory when a user closes Outlook. Unlike add-ins created with Microsoft Visual Basic 6.0, the memory management techniques in Visual Basic .NET rely on garbage collection provided by the common language runtime. Garbage collection for managed code resources is automatic. However, the unmanaged code objects represented by the Outlook object model require explicit clean-up. Unless you garbage collect your event-aware objects correctly, you will experience problems with objects that are not destroyed and Outlook will remain in memory.
In the sample application, a reference has already been set to Microsoft Outlook 10.0 Object Library. Figure 4 illustrates Outlook listed as a reference in the Solution Explorer window. Notice that the Properties window to the right of the Solution Explorer shows that the path for the interop assembly for Outlook points to the Outlook Primary Interop Assembly in the GAC.
Figure 4. Outlook Reference in the Solution Explorer
If you are creating your own solution that requires a reference to Outlook, use the Add References dialog box to add an Outlook reference. The Add References dialog box is shown in Figure 5.
Figure 5. The Add References dialog box
To add a reference to Outlook:
Once you have added Outlook as a reference, you should add an Imports statement so that you don't have to fully qualify Outlook objects by typing, for example, Microsoft.Office.Interop.Outlook.MailItem
. An Imports statement appears after any Option statements but before any type declarations. In the case of the sample application, an Import alias is used for Outlook in the Connect and OutlAddIn classes as follows:
Imports Extensibility
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports Outlook = Microsoft.Office.Interop.Outlook
Imports Microsoft.Office.Core
Imports Microsoft.Win32
All COM add-ins must implement the IDTExtensibility2 interface. Unlike the Add-in Designer in Visual Basic 6.0, which exposes a property page user interface (UI) for your Connect class, Visual Studio. NET does not support UI for IDTExtensibility2. You must manually create required add-in registry settings such as LoadBehavior, Description and FriendlyName by using the Registry Editor for your Setup project, discussed later. The IDTExtensibility2 interface acts as a connector between your managed code add-in and Outlook, which acts as a host application for connected add-ins. Five events are exposed by the IDTExtensibility2 interface:
The following code shows you how the OnConnection event works in Visual Basic. NET. We'll discuss the important code statements in the OnConnection event in the following sections. If you are familiar with COM add-in OnConnection code in Visual Basic 6.0, you will notice that this code looks familiar, but there are some important new elements, including:
Try Catch End Try
block to trap errors. 'Implements is required in Connect class declarations.
Implements Extensibility.IDTExtensibility2
'The OutAddIn class separates add-in code from the Connect class.
Dim m_BaseClass As New OutAddIn()
Private Sub OnConnection(ByVal application As Object, _
ByVal connectMode As Extensibility.ext_ConnectMode, _
ByVal addInInst As Object, ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnConnection
Dim oApp As Outlook.Application
Dim oType As Type
Dim GetProgID As Object
Dim MyProgID As String
Dim oArgs As Object()
Dim Reg As RegistryKey
Dim strKey As String
Try
'Use InvokeMember to get the ProgID of addInInst object.
oType = addInInst.GetType
GetProgID = oType.InvokeMember("ProgID", _
BindingFlags.Public Or BindingFlags.GetField Or _
BindingFlags.GetProperty, _
Nothing, _
addInInst, _
oArgs)
MyProgID = CType(GetProgID, String)
'Look for the WriteDiagnostics value.
'If this key is found, DebugWriter writes to addinerr.txt.
strKey = "Software\Microsoft\Office\Outlook\Addins\" _
& MyProgID
Reg = Registry.CurrentUser.OpenSubKey(strKey)
Dim arrayNames As Array = Reg.GetValueNames
If arrayNames.BinarySearch(arrayNames, "WriteDiagnostics") _
< 0 Then
m_blnWriteDiagnostics = False
Else
m_blnWriteDiagnostics = True
End If
'Close the key.
Reg.Close()
DebugWriter("OnConnection Called")
'Convert application from generic object to
'Outlook.Application.
oApp = CType(application, Outlook.Application)
'Call InitHandler.
m_BaseClass.InitHandler(oApp, MyProgID)
Catch ex As SystemException
DebugWriter("OnConnection Exception: {0}", ex.Message)
End Try
End Sub
OutlookCOMAddinVBNET is built using a dual-class architecture. The Connect class contains the generic IDTExtensibility2 plumbing for the add-in, and the OutAddin class contains the application-specific code and event-aware objects for the add-in. The OutAddin class is initialized by the Dim m_BaseClass As New OutAddIn()
statement in the Connect class. Both the OutAddin and Connect classes must contain unique <GuidAttribute> and <ProgIDAttribute> attributes in order for the classes to be recognized by COM. For example, here are the attributes for the OutlookCOMAddinVBNET.Connect class:
<GuidAttribute("14CF4472-86DE-48b0-9FD1-E5CA7673F9A9"), _
ProgIdAttribute("OutlookCOMAddinVBNET.Connect")> _
Public Class Connect
'Implements is required.
Implements Extensibility.IDTExtensibility2
'The IDTExensibility event procedures goes here.
'Additional code...
End Class
Note If you modify OutlookCOMAddinVBNET to serve as the basis for your own add-in, you should modify both Guids and ProgIDs for both Connect and OutAddin classes. Use the Create GUID command on the Tools menu to generate new GUIDs for your project. Be sure to remove the {} (braces) from the GUID after you paste the new GUIDs into the <GuidAttribute>.
In the OutlookCOMAddinVBNET project, Option Strict has been turned ON in the Project Properties dialog box, as shown in Figure 6. Option Strict requires explicit declaration of all object variables (by implicitly turning on Option Explicit) and prevents widening type conversions between one object type and another. If you set Option Strict to off, your add-in code will behave more like add-ins created in Visual Basic 6.0. However, your code is then subject to potential type-conversion errors that will be exposed at run time rather than design time.
Figure 6. Use Project Properties dialog box to turn Option Strict on
Setting Option Strict to on has certain implications for the different object types supported by the Outlook Object Model. For example, the Outlook Object model uses the generic Item type to represent MailItem, AppointmentItem, ContactItem, and other specific Outlook classes. In the sample application, the generic Outlook Item is handled by a Friend class named OutlookItem. This class makes it possible to retrieve Outlook Item properties such as MessageClass, Subject, EntryID, and StoreID from Item objects. Specific Item properties are returned as properties of the OutlookItem class.
For example, the following code from OutlookItem shows you how the ObjectClass property is returned for an OutlookItem object. CbbZipResolveOnOff is a procedure that receives two arguments, oItem As Object
and blnShow As Boolean
. These arguments determine whether or not the Resolve Zip command bar button is shown on an Outlook Inspector. Since Option Strict is on, an implicit type conversion between oItem and the Outlook class represented by that Item is not allowed. To work around this restriction, the OutlookItem class retrieves a subset of properties held in common by MailItems, PostItems, AppointmentItems and so forth. Most of the work in the OutlookItem class is performed by the GetPropValue function. GetPropValue uses the overloaded InvokeMember method to retrieve an object that represents the specific property name passed as an argument to GetPropValue. InvokeMember is useful when Option Strict is on and you need to determine the value of an object property.
Private Sub CbbZipResolveOnOff(ByVal oItem As Object, _
ByVal blnShow As Boolean)
Dim cbStandard As CommandBar
Dim oAI As Outlook.AppointmentItem
Dim oJI As Outlook.JournalItem
Dim oCI As Outlook.ContactItem
Dim oDI As Outlook.DistListItem
Dim oTI As Outlook.TaskItem
Dim oPI As Outlook.PostItem
Dim oMI As Outlook.MailItem
'Use the OutlookItem class to retrieve Item properties.
Dim MyItem As New OutlookItem(oItem)
DebugWriter("CbbZipResolveOnOff Called")
Try
Select Case MyItem.ObjectClass
Case Outlook.OlObjectClass.olAppointment
oAI = CType(oItem, Outlook.AppointmentItem)
cbStandard = oAI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olMail
oMI = CType(oItem, Outlook.MailItem)
cbStandard = oMI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olContact
oCI = CType(oItem, Outlook.ContactItem)
cbStandard = oCI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olDistributionList
oDI = CType(oItem, Outlook.DistListItem)
cbStandard = oDI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olPost
oPI = CType(oItem, Outlook.PostItem)
cbStandard = oPI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olTask
oTI = CType(oItem, Outlook.TaskItem)
cbStandard = oTI.GetInspector.CommandBars("Standard")
Case Outlook.OlObjectClass.olJournal
oJI = CType(oItem, Outlook.JournalItem)
cbStandard = oJI.GetInspector.CommandBars("Standard")
End Select
If Not cbStandard Is Nothing Then
CBBZipResolve = _
CType(cbStandard.FindControl(Tag:="ZipResolve"), _
CommandBarButton)
If CBBZipResolve Is Nothing Then
CBBZipResolve = _
CType(cbStandard.Controls.Add _
(Type:= MsoControlType.msoControlButton, _
Parameter:="ZipResolve"), CommandBarButton)
End If
With CBBZipResolve
.DescriptionText = _
"Resolve Zip Codes and Addresses using Web Service"
.BeginGroup = True
.Caption = "&Resolve Zip"
.FaceId = 4211
.Tag = "ZipResolve"
.TooltipText = _
"Resolve Zip Codes and Addresses using Web Service"
.Style = MsoButtonStyle.msoButtonIconAndCaption
.OnAction = "!<" & m_ProgID & ">"
.Visible = blnShow
End With
End If
Catch ex As SystemException
DebugWriter("CbbZipResolveOnOff Exception: {0}", ex.Message)
End Try
End Sub
Public ReadOnly Property ObjectClass() As Outlook.OlObjectClass
Get
Return CType(GetPropValue(OlClass), Outlook.OlObjectClass)
End Get
End Property
Public Function GetPropValue(ByVal strPropName As String) As Object
Try
GetPropValue = m_Type.InvokeMember(strPropName, _
BindingFlags.Public Or BindingFlags.GetField Or _
BindingFlags.GetProperty, _
Nothing, _
m_Item, _
m_Args)
Catch ex As SystemException
Debug.WriteLine(String.Format _
("OutLookItem: GetPropValue for {0} Exception: {1} ", _
strPropName, ex.Message))
End Try
End Function
The InitHandler procedure in the OutAddin class does the work of initializing event-aware objects for OutlookCOMAddinVBNET. It is declared using the Friend keyword so that it can be called from OnConnection in the Connect class but is not visible publicly. It also creates a command bar button on the Standard toolbar of the Outlook Explorer window. The command bar button displays a .NET Windows form that displays information about the OutlookCOMAddinVBNET application.
Friend Sub InitHandler(ByVal oApp As Outlook.Application, _
ByVal strProgID As String)
Dim oCommandBars As CommandBars
Dim oStandardBar As CommandBar
DebugWriter("InitHandler Called")
Try
'Declared WithEvents
m_olOutlook = oApp 'Application object
'Instantiate public module-level Outlook application variable.
m_olApp = oApp
'The ProgID string is required for CommandBarControls.
m_ProgID = strProgID
'Instantiate event-aware objects.
m_olNamespace = m_olOutlook.Session
m_olExplorers = m_olOutlook.Explorers
m_olInspectors = m_olOutlook.Inspectors
'Type conversion is required due to "ambiguous name" issue.
m_olExplorer = CType(m_olOutlook.ActiveExplorer, _
Outlook.ExplorerClass)
'Create command bar button.
'Set up a custom button on the "Standard" command bar.
oCommandBars = m_olExplorer.CommandBars
oStandardBar = oCommandBars.Item("Standard")
' In case the button was not deleted, use the existing one.
CBBAbout =CType(oStandardBar.FindControl _
(Tag:="Visual Basic.NETSample"), CommandBarButton)
If CBBAbout Is Nothing Then
CBBAbout = CType(oStandardBar.Controls.Add _
(Type:=MsoControlType.msoControlButton, _
Temporary:=False), CommandBarButton)
With CBBAbout
.Caption = ".NET"
.Style = MsoButtonStyle.msoButtonIconAndCaption
.FaceId = 472
.TooltipText = _
"About Outlook COM Add-in for Visual Basic.NET"
.BeginGroup = False
' Use the Tag property to find the control.
.Tag = "Visual Basic.NETSample"
.OnAction = "!<" & m_ProgID & ">"
' Make the button visible.
.Visible = True
End With
End If
Catch ex As SystemException
DebugWriter("InitHandler Exception: {0}", ex.Message)
End Try
End Sub
Event-aware objects such as m_olOutlook, m_olNamespace, m_olExplorers, m_olExplorer, and m_olInspectors are instantiated in InitHandler. These objects respond to events raised by the Outlook Object Model. The events themselves are COM events, and the common language runtime provides the connection points so that the add-in's managed code can respond to COM events hosted by Outlook. Visual Basic .NET provides two methods of hooking events:
Using the traditional Dim WithEvents m_olOutlook As Outlook.Application
and then writing event procedure code using a Handles clause. This approach is used in the OutlookCOMAddinVBNET project.
Dim WithEvents m_olOutlook As Outlook.Application
'Declare additional event-aware object declarations as needed. Private Sub m_olOutlook_NewMail() Handles m_olOutlook.NewMail 'Event code goes here End Sub
Using the new AddHandler and RemoveHandler statements in Visual Basic .NET. These statements can dynamically connect and disconnect event handlers from event procedures in your code. Unlike the WithEvents approach, AddHandler and RemoveHandler allow you to associate multiple event handlers with a single event.
Private Sub TestLocalEventHander()
Dim m_olApp As New Outlook.Application()
AddHandler m_olApp.NewMail, AddressOf Me.EventHandlerNewMail
'Additional code
RemoveHandler m_olApp.NewMail, AddressOf Me.EventHandlerNewMail
End Sub
Private Sub EventHandlerNewMail()
'Event code goes here.
End Sub
Note Do not use WithEvents and AddHandler/RemoveHandler for the same event.
The current version of the Outlook Primary Interop Assembly and the Visual Basic .NET compiler combine to produce a problem when you attempt to declare event-aware objects using certain Outlook classes. For example, if you Dim m_olExplorer As Outlook.Explorer
and then attempt to create an event procedure for the Close event, Visual Basic .NET will display the following design-time error in the Task List:
Note 'Close' is ambiguous across the inherited interfaces 'Microsoft.Office.Interop.Outlook._Explorer' and 'Microsoft.Office.Interop.Outlook.ExplorerEvents_10_Event'.
To prevent this message, and to allow your project to compile, you must use the RCW or .NET coclass for declarations of certain Outlook objects that cause the ambiguous name design-time error. For example, the following syntax works correctly for the declaration of the event-aware m_olExplorer object and the m_olExplorer_Close event. m_olExplorer is declared as an ExplorerClass object rather than an Explorer object:
'Declarations are made using WithEvents and coclass.
Dim WithEvents m_olExplorer As Outlook.ExplorerClass
Private Sub m_olExplorer_Close() _
Handles m_olExplorer.ExplorerEvents_Event_Close
'Event code goes here. End Sub
Note Look for this problem to be fixed in future versions of the Visual Basic .NET compiler.
One of the most compelling aspects of development using Visual Basic .NET is the easy integration of XML Web services into your project. If you select the Add Web Reference command on the Project menu, Visual Studio displays the Add Web Reference dialog box. Visual Studio then creates all the necessary code behind the scenes to add the XML Web service to your project and call the properties and methods of objects provided by the XML Web service. The following code for CBBZipResolve calls the XML Web service to resolve the postal address for m_olContactItem, which represents a ContactItem object. Although the ZipResolver Web service returns the postal address in uppercase, the following code uses the built-in .NET CultureInfo class to return a proper-cased string for the postal address street and city.
Private Sub CBBZipResolve_Click(ByVal Ctrl As _
Microsoft.Office.Core.CommandBarButton, _
ByRef CancelDefault As Boolean) Handles CBBZipResolve.Click
Dim strAddress, strCity, strState As String
Try
If IsUSPostalAddress(m_olContactItem) Then
strAddress = m_olContactItem.MailingAddressStreet
strCity = m_olContactItem.MailingAddressCity
strState = m_olContactItem.MailingAddressState
Dim fWait As New frmWait()
Dim oCorrectedAddress As _
New net.eraserver.webservices.USPSAddress()
Dim oZipResolve As _
New net.eraserver.webservices.ZipCodeResolver()
'Display fWait since you cannot display hourglass.
fWait.Show()
System.Windows.Forms.Application.DoEvents()
oZipResolve.Url = _
"http://webservices.eraserver.net/zipcoderesolver/" &
_
"zipcoderesolver.asmx?WSDL"
oCorrectedAddress = _
oZipResolve.CorrectedAddressXml _
("0", strAddress, strCity, strState)
fWait.Close()
'oCorrectedAddress.Street is Nothing when not resolved.
If oCorrectedAddress.Street Is Nothing Then
MsgBox("Unable to resolve this address.", _
MsgBoxStyle.Information, _
"Outlook COM Add-in for VB.NET")
Else
Dim oContact As Outlook.ContactItem = _
CType(m_olApp.ActiveInspector.CurrentItem, _
Outlook.ContactItem)
With oContact
.MailingAddressStreet = _
GetProper(oCorrectedAddress.Street)
.MailingAddressCity = _
GetProper(oCorrectedAddress.City)
.MailingAddressState = oCorrectedAddress.State
.MailingAddressPostalCode = _
oCorrectedAddress.FullZIP
End With
End If
Else
MsgBox("Not a valid postal address!", _
MsgBoxStyle.Exclamation, _
"Outlook COM Add-in for VB.NET")
End If
Catch ex As System.Exception
MsgBox("Error invoking Zip Code Resolver Web Service." _
& vbCrLf & ex.Message, MsgBoxStyle.Information, _
"Outlook COM Add-in for VB.NET")
End Try
End Sub
'This function converts USPS addresses (upper-cased) to initial caps.
string
Private Function GetProper(ByVal strInput As String) As String
On Error Resume Next
If Len(strInput) = 0 Then
Exit Function
End If
Dim oCI As New CultureInfo("en-us")
GetProper = oCI.TextInfo.ToTitleCase(LCase(strInput))
End Function
One of the most difficult aspects of Outlook add-in programming is ensuring that your add-in does not cause Outlook to remain in memory when the user exits Outlook. Outlook shutdown occurs when there are no longer any open inspectors or explorers. Outlook is unique within the Office suite in that it has two classes for its user interface, represented by the Outlook explorer and inspector. Outlook is still open if the main Outlook Explorer window is closed but an inspector window remains open. Detection of shutdown is complicated because there are certain invocations of Outlook (such as Send To Mail Recipient from the shortcut menu of Microsoft Windows Explorer) that do not increment the count of explorer or inspector windows. If any object references are still alive in your add-in when Outlook shuts down, your add-in will cause Outlook to remain in memory.
Normally, the OnDisconnection event exposed by the IDTExtensibility2 interface would provide a means for your code to perform object clean-up and ensure a correct shutdown sequence. The OnDisconnection event, however, does not fire reliably when the shutdown mode is equal to ext_DisconnectMode.ext_dm_HostShutdown.
The known workaround to this problem is to use the Close event for both Inspector and Explorer objects. The Close event calls UnInitHandler, which in turn performs object clean-up. Since the Close event for these objects triggers the "Ambiguous across inherited interfaces" design-time error described previously, you must declare event-aware objects using ExplorerClass and InspectorClass respectively. The following code from the OutAddin class will correctly detect the shutdown of the Outlook user interface.
'Declarations made using WithEvents and coclass.
Dim WithEvents m_olExplorer As Outlook.ExplorerClass
Dim WithEvents m_olInspector As Outlook.InspectorClass
'Explorer Close event code:
Private Sub m_olExplorer_Close() _
Handles m_olExplorer.ExplorerEvents_Event_Close
Try
DebugWriter("Explorer_Close Called", "")
m_olExplorer = CType(m_olApp.ActiveExplorer, _
Outlook.ExplorerClass)
If (m_olExplorer Is Nothing) And _
(m_olApp.Inspectors.Count = 0) Then
UnInitHandler()
End If
Catch ex As SystemException
DebugWriter("Explorer_Close Exception: {0}", ex.Message)
End Try
End Sub
'Inspector Close event code:
Private Sub m_olInspector_Close() _
Handles m_olInspector.InspectorEvents_Event_Close
Try
DebugWriter("Inspector_Close Called", "")
If m_olApp.ActiveExplorer Is Nothing And _
m_olApp.Inspectors.Count <= 1 Then
UnInitHandler()
End If
Catch ex As SystemException
DebugWriter("Inspector_Close Exception: {0}", ex.Message)
End Try
End Sub
Once the Close event calls the UnInitHandler procedure shown below, you must correctly dispose of your objects. If you do not dispose of all objects declared and instantiated in your add-in, then Outlook will remain in memory after exiting. The object clean-up code is positioned in a Try...Catch...Finally...End Try
block. With add-ins created in Visual Basic 6.0, the correct coding practice would be to set all object variables to Nothing during the UnInitHandler procedure. For COM objects in Visual Basic 6.0, setting the object to Nothing killed the object reference and released the object from memory. In .NET add-ins that interoperate with COM-based Outlook, disposal of COM objects is more complex. The DisposeObject procedure is called on all class or module level object variables that are still in scope. DisposeObject calls Marshal.ReleaseCOMObject(obj)
until the reference count equals zero. Marshal.ReleaseCOMObject
provides deterministic release of COM objects. In the Finally
block of UnInitHandler, GC.Collect
and GC.WaitforPendingFinalizers
cause the common language runtime to clean up any remaining RCW objects and ensure a clean shutdown of Outlook.
Friend Sub UnInitHandler()
'You must dereference all objects in this procedure
'or Outlook will remain in memory.
Dim oCommandBars As CommandBars
Dim oStandardBar As CommandBar
Dim oCBB As CommandBarButton
Try
If m_blnRunUnInitHandler Then
Exit Sub
Else
m_blnRunUnInitHandler = True
End If
DebugWriter("UnInitHandler Called")
' Delete the Explorer command bar button.
If Not (CBBAbout Is Nothing) Then
CBBAbout.Delete()
End If
' Delete the Inspector command bar button.
CbbZipResolveRemove()
' Next release all Outlook objects dim'ed WithEvents.
DisposeObject(m_olMailItem)
DisposeObject(m_olPostItem)
DisposeObject(m_olAppointmentItem)
DisposeObject(m_olContactItem)
DisposeObject(m_olDistListItem)
DisposeObject(m_olJournalItem)
DisposeObject(m_olTaskItem)
DisposeObject(m_olInspector)
DisposeObject(m_olExplorer)
DisposeObject(m_olInspectors)
DisposeObject(m_olExplorers)
DisposeObject(m_olReminders)
DisposeObject(m_olResults)
DisposeObject(m_olViews)
DisposeObject(m_olNamespace)
DisposeObject(m_olApp)
DisposeObject(m_olOutlook)
Catch ex As SystemException
DebugWriter("UnInitHandler Exception: {0}", ex.Message)
Finally
' Do final garbage collection.
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Sub
Private Sub DisposeObject(ByVal obj As Object)
Dim count As Integer
DebugWriter("DisposeObject Called")
Try
If obj Is Nothing Then
Exit Try
End If
count = Marshal.ReleaseComObject(obj)
DebugWriter(String.Format("DisposeObject - Release {0}, _
RefCount: {1}", obj.ToString(), count), "")
While count > 0
count = Marshal.ReleaseComObject(obj)
End While
Catch ex As SystemException
DebugWriter("DisposeObject Exception: {0}", ex.Message)
Finally
obj = Nothing
End Try
End Sub
You've learned about the critical working code of an Outlook add-in built with Visual Basic .NET. Accompanying the sample OutlookCOMAddinVBNET project is the setup project named OutlookCOMAddinVBNETSetup. There are several important setup issues that you must address when you deploy an add-in developed with Visual Basic .NET:
You must install the Microsoft .NET Framework (approximately 20 MB) on the target machine before you install your add-in.
You must install the Outlook Primary Interop Assembly on the target machine. See the "To Use the Outlook 2002 PIA in Your Solution" section above.
If your Setup project installs the add-in registry keys under HKEY_LOCAL_MACHINE (HKLM) instead of HKEY_CURRENT_USER (HKCU), the add-in will not be visible in the Outlook COM Add-ins dialog box.
The COM Add-ins dialog box will display the common language runtime engine, mscoree.dll, as the source of your add-in instead of the name of your managed code add-in as shown in Figure 7. In the case of the sample application, the actual file name is OutlookCOMAddinVBNET.dll.
Figure 7. The COM Add-Ins dialog box
At this point, there is no convenient solution to the requirement of installing the .NET Framework. Later versions of Microsoft Windows or Office may have the .NET Framework installed so that no additional distribution is required.
Microsoft recommends that you do not distribute solutions using the TLBIMP-generated interop assembly. The current recommendation is to use the Outlook primary interop assembly, which is currently available only for Outlook 2002. As of this writing, there is no forthcoming primary interop assembly for Office 2000 and Outlook 2000.
To distribute your add-in to other users, you must follow these steps:
Be certain to add the Outlook Primary Interop Assembly to your Setup project using the steps outlined above in the "To use the Outlook 2002 PIA in your solution" section.
You can exclude stdole.dll and office.dll from the list of Detected Dependencies in your Setup Project.
Right-click YourProjectSetupName in the Solution Explorer, and then click Build. This creates Setup.exe and the required .msi files in the YourProjectSetupName\Debug or YourProjectSetupName\Release folder, depending on the type of build.
Copy the contents of the YourProjectSetupName\Debug or YourProjectSetupName\Release folder to the target computer on which you want the add-in installed.
Start Setup.exe to install the .NET Shared add-in.
Note In the sample OutlookCOMAddinVBNETSetup project, all these steps have been completed for you. Be aware that the OutlookCOMAddinVBNET Setup project, in order to minimize download size, does not include Windows Installer Bootstrap files.
The load behavior of an Outlook COM add-in is controlled by settings in the Windows registry. If the add-in is registered under HKLM, the add-in will not be visible in the Outlook COM Add-ins dialog box. Registration under HKLM prevents a user from disconnecting the add-in through the COM Add-ins dialog box. Table 1 illustrates the DWORD values for LoadBehavior. These values apply to both HKCU and HCLM.
Table 1. LoadBehavior registry settings
Initial Load Behavior Setting | LoadBehavior DWORD | Behavior Description |
---|---|---|
None | 0x00
0x01 (Connected) |
The COM add-in is not loaded when Outlook boots. It can only be loaded programmatically. |
Startup | 0x02
0x03 (Connected) |
The add-in is loaded when Outlook boots. Once the add-in is loaded, it remains loaded until it is explicitly unloaded. |
Load On Demand | 0x08
0x09 (Connected) |
The add-in is not loaded until the user clicks the button or menu item that loads the add-in, or until a procedure sets its Connect property to True. |
Load At Next Startup Only | 0x10
(Reverts to 0x09 on next boot) |
After the COM add-in has been registered, it loads as soon as the user runs Outlook for the first time, and it creates a button or menu item for itself. The next time the user boots Outlook, the add-in is loaded on demand; that is, it doesn't load until the user clicks the button or menu item associated with the add-in. |
You must create registry values in your Setup project for LoadBehavior, Description and FriendlyName. These values are stored under HKCU or HKLM\Software\Microsoft\Office\Outlook\Addins\ProgID.
ProgID represents the programmatic identifier of your add-in. The keys for the sample OutlookCOMAddinVBNETSetup project are shown in the Registry Editor in Figure 8. When the add-in is installed by your Setup project, the registry settings cause the add-in to load during Outlook startup.
Figure 8. Use the Registry Editor to create add-in registry settings
Note You cannot use the Outlook COM Add-ins dialog box to add a managed code add-in to the list of installed add-ins manually. Outlook will report that '<addin.dll> is not a valid Office add-in.' If you want to deploy the sample add-in, you must use the .msi file created by your Visual Studio .NET Setup Project.
If you created your add-in solution with the Visual Studio Add-in Wizard, you should understand that the choices you make in the Add-in Wizard control the registry hive (HKLM or HKCU) where your add-in is registered. For additional details, please see PRB: Visual Studio .NET Shared Add-in Is Not Displayed in Office COM Add-ins Dialog Box (Q316723) in the Microsoft Knowledge Base.
One final setup issue is the appearance that your managed code add-in is supplied by mscoree.dll, the common language runtime engine. In fact, what you see in the COM Add-in dialog box is more than an appearance. It's actually correct that from the perspective of COM, your add-in's InprocServer32 setting points to mscoree.dll. The InprocServer32 key contains a CodeBase value that points to the location of your managed code add-in assembly. This dependence on mscoree.dll presents a problem when you attempt to add your add-in to the list of trusted add-ins in the Outlook Security Form in the Outlook Security Settings public folder. Although a complete discussion of this issue is beyond the scope of this article, you should be aware of the following:
There are some important coding practices that you must follow when you create an Outlook add-in using Visual Basic .NET. You must also be aware of the correct setup requirements when you deploy your add-in to a target machine. The built-in classes in the .NET Framework offer significant advantages over writing and maintaining helper code in Visual Basic 6.0. In terms of consuming XML Web services, a .NET add-in does not require the proxy classes that are necessary for a Visual Basic for Applications or Visual Basic 6.0 add-in. If you are creating an Outlook add-in that uses an XML Web service, and you can manage the installation of the .NET Framework, you should consider writing your add-in with Visual Basic .NET.
Please sign in to use this experience.
Sign in