Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Only receive NewInspector and Click events for a short while (dotnet)...

3 views
Skip to first unread message

Gordon Watts

unread,
Dec 27, 2001, 6:21:30 PM12/27/01
to
Hi,
Thanks to many people's help from the news groups I've got a basic
COM addin working. It correctly sinks the NewInspector event, adds a
command bar button (if needed), and hooks up to that code to sink the
Click event.

Now, the weird thing -- I only get the NewInspector (or Click) event
a limited number of times. And the number of times seems to be
directly related to the number of memory allocations I make. For
example, I was allocating various things over and over again (not
really required), releasing them after each use, and after opening 3
or 4 new inspector windows, I'd stop getting the events. I rewrote my
code, cutting down as much as possible the number of allocations --
eliminated them (I think) -- and I was able to run forever. Of course,
doing its task, my addin needs to call my addin needs to allocate
memory.

For example, this first manifest itself because I have a small debug
library that writes a log file. This way I can trace what is going on
even when I'm not tracking it in the debugger. My first version of
these utilities would open/append a file everytime they called (and
create the StreamWriter object). In this situation I would get about
the first 4 or 5 NewInspector events. I changed the code so it only
opens the file when the debug utility is first created (and only
creates the StreamWriter object) and I was able to get 12 or so
NewInspector events. This behavior is reproducable.

My first thought was this had to do with garbage collection (ok, it
wasn't my first thought, it took several hours of eliminating code to
arrive at this guess). Some key object that is getting used is getting
released during garbage collection. I've done my best to make sure
that every single object I allocate is referenced by my addin class,
and then I created a static variable that contains a reference to my
class. No difference. One thing that would be nice to see is if this
problem occured after the garbage collector ran, but I can't figure
out how to tell (didn't see any docs in the .NET framework
documentation).

I'm running on Windows 2000, sp2 (all security fixes), office 2002,
and RC1 of VS.NET.

Any help is much appreciated! Would really like to know why the
NewInspector callback is no longer called!

Cheers,
Gordon.

Frank Murphy

unread,
Dec 27, 2001, 6:42:38 PM12/27/01
to
I'd love to see your code of your working add-in that responds to events.
I've been banging my head against the wall trying to get a .NET COM add-in
to respond to any Outlook event :)

Thanks!


"Gordon Watts" <gwa...@phys.washington.edu> wrote in message
news:3b2256.011227...@posting.google.com...

Jay B. Harlow [MVP - Outlook]

unread,
Dec 27, 2001, 9:28:53 PM12/27/01
to
Gordon,
I have not started playing in this area, I intend to!

Can you post or send privately some of your code. So I may review it, and
see anything obvious or not so obvious.

Thanks
Jay

"Gordon Watts" <gwa...@phys.washington.edu> wrote in message
news:3b2256.011227...@posting.google.com...

Mike Timms

unread,
Dec 27, 2001, 10:23:26 PM12/27/01
to
Gordon,

Are you releasing the Outlook objects properly? You need to call
Marshal.ReleaseComObject(object) on each Outlook object you create.

In addition the OnDisconnection method needs to use
Marshal.ReleaseComObject to clean up the Outlook Application object
and any other Outlook objects that were allocated in the OnConnection
method. Finally it seems to be necessary to invoke the garbage
collector - GC.Collect() - to avoid Outlook hanging around after the
Add-in disconnects.

My OnDisconnection method looks like this:

public void OnDisconnection(Extensibility.ext_DisconnectMode
disconnectMode, ref System.Array custom)
{

// Release Outlook objects
Marshal.ReleaseComObject(m_olApp); // Application
Marshal.ReleaseComObject(m_olNS); // NameSpace
Marshal.ReleaseComObject(m_olInspectors); // Inspectors collection
GC.Collect(); // this appears to be necessary to avoid Outlook
hanging around in memory

}

- Mike


gwa...@phys.washington.edu (Gordon Watts) wrote in message news:<3b2256.011227...@posting.google.com>...

Mike Timms

unread,
Dec 27, 2001, 10:30:56 PM12/27/01
to
Frank,

In the RC versaion, there's currently a bug in the COM wrapper code
that's generated for the Outlook object model. The wrapper needs to be
rebuilt to properly handle Outlook events. See the thread
"TLBIMP-generated code doesn't do events right?" for details.

http://groups.google.com/groups?hl=en&threadm=3b2256.0112271502.7b26dd44%40posting.google.com&prev=/groups%3Fhl%3Den%26group%3Dmicrosoft.public.dotnet.framework.interop

- Mike

"Frank Murphy" <fra...@hotmail.com> wrote in message news:<OX12UBzjBHA.1540@tkmsftngp05>...

Frank Murphy

unread,
Dec 27, 2001, 10:48:43 PM12/27/01
to
I read those excellent posts and made the changes. When I call AddHandler
in VB I still get the NoInterface error.

Mike, do you have some code or sample app you can share that works? I don't
think I'm the only one having the issues with Outlook :)

Appreciate it.

Frank

"Mike Timms" <mti...@sympatico.ca> wrote in message
news:e1d085c2.01122...@posting.google.com...

Gordon Watts

unread,
Dec 28, 2001, 12:05:34 AM12/28/01
to
Hi,
Wow -- this message generated a lot of replies. Thanks so much. I
think I've got it.

- I was correcting the Interop.Outlook.dll as per that thread you
referenced, thanks Mike.
- But, in the end, Mike, you got it, indirectly. It wasn't that I
wasn't releasing the objects... it was that I never held onto the
Inspectors object! Once I made that a member variable, I was able to
survive the garbage collection! Very nice. I suspect this mistake
comes from me not knowing (very well) the interface to outlook and its
programming model. Is this something I would have had to do in C++, or
is this unique to C#?
- I added your release code, Mike, but it makes no difference as to
weather or not my outlook stays in memory, or how long it does. It
always has been running about 5 seconds after the last window closed.
The RelaseComObject doens't seem to make any difference. I've added
the code anyway.

Since I've had several requests, I'll post the source code below.
Please excuse the resulting length of this news message. This is the
Connect.cs file, the outline of which is generated by the wizzard when
creating a COM addin project (for me... I created this guy to work
with Outlook only). And I'm really sorry about the formatting...
limitation of the tool, I'm afraid.

namespace OfficeSendvCard
{
using System;
using Extensibility;
using System.Runtime.InteropServices;
using Microsoft.Office.Core;

#region Read me for Add-in installation and setup information.
// When run, the Add-in wizard prepared the registry for the Add-in.
// At a later time, if the Add-in becomes unavailable for reasons
such as:
// 1) You moved this project to a computer other than which is was
originally created on.
// 2) You chose 'Yes' when presented with a message asking if you
wish to remove the Add-in.
// 3) Registry corruption.
// you will need to re-register the Add-in by building the
MyAddin21Setup project
// by right clicking the project in the Solution Explorer, then
choosing install.
#endregion

///
/// I found a lot of helpful information in the following web sites:
///
/// -- http://www.cswl.com/whiteppr/tech/EmailEncryption.html A break
down of how to create a COM addin that looks at email messages
/// -- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnout2k/html/o2kwhatsnew.asp
-- New programming interfaces, etc. from MS for OL2000
/// -- http://groups.google.com/groups?hl=en&threadm=e1d085c2.0112190501.6fdc5c20%40posting.google.com&rnum=2&prev=/groups%3Fq%3Dtimms%26hl%3Den%26group%3Dmicrosoft.public.dotnet.framework.*%26rnum%3D2%26selm%3De1d085c2.0112190501.6fdc5c20%2540posting.google.com
/// How to deal with the RC1 bug in the generation of the outlook
dll interface (prevents the += in the OnStartupComplete
/// method from working):
/// - First, disassemble the Interop.Outlook.dll file: ildasm
/out=out.txt Interop.Outlook.dll
/// - Edit the resulting output file. Look for
InspectorEvents_SinkHelper. Change the "private" specifier
/// to be pubilc, and save the file. In the end, for me to get
this to work correctly, I had to go through
/// and change every single SinkHelper refernce to be public...
probably missed something dump...
/// - Rebuild the DLL: ilasm /resource=out.res /quiet /dll
out.txt
/// -- http://www.microeye.com/images/OL2Kmap.gif -- Object model
(huge gif).
/// -- http://msdn.microsoft.com/code/default.asp?url=/msdn-files/026/000/118/Source%20Files/Connect_dsr.asp
--
/// Mass mail COM ADIN sample, written in VB (good example of
structure, how to add buttons, etc.).
/// -- http://groups.google.com/groups?hl=en&threadm=3b2256.0112271521.53f9c44f%40posting.google.com&rnum=1&prev=/groups%3Fq%3DNewInspector%2Bevents%2BGordon%2BWatts%26hl%3Den
/// It was pointed out I need to keep the Inspectors' collection
around. If you don't, eventually the GC
/// goes off and deletes it, and then you loose all of event
callback connections. Further, because you've
/// grabbed it, you need to release it in the disconnection method.
///
/// This will leave you with a out.dll file. Make sure the CopyLocal
is set to false in the properties of the
/// Outlook reference (under the References tab under the project).
And make sure you put the dll in the bin
/// directory.

/// <summary>
/// The object for implementing an Add-in.
/// </summary>
/// <seealso class='IDTExtensibility2' />
[GuidAttribute("1470D3D4-F06D-4CC0-830A-E4AE6BF13C9C"),
ProgId("OfficeSendvCard.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2
{

/// <summary>
/// Holds the debug writing interface for log file debugging of this
component.
/// </summary>
private debug_utils.logFileWriter _writer;

/// <summary>
/// Starting up the plugin here. Create our debugging utilities.
/// </summary>
public Connect()
{
//
// Setup the log file writer
//

_writer = new debug_utils.logFileWriter ();
_writer.createNewLog = true;
_writer.logfileName = "c:\\outlook_vcard.log";

_writer.writeLine ("Starting up the addin");
}

/// <summary>
/// Implements the OnConnection method of the IDTExtensibility2
interface.
/// Receives notification that the Add-in is being loaded.
/// </summary>
/// <param term='application'>
/// Root object of the host application.
/// </param>
/// <param term='connectMode'>
/// Describes how the Add-in is being loaded.
/// </param>
/// <param term='addInInst'>
/// Object representing this Add-in.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode, object addInInst, ref
System.Array custom)
{
_outlookApp = (Outlook.Application) application;
_addInInstance = addInInst;

_writer.writeLine ("Got connection message. There are " +
_outlookApp.COMAddIns.Count + " other addins");
_writer.writeLine (" -> Outlook version is " +
_outlookApp.Version);

}

/// <summary>
/// Implements the OnDisconnection method of the
IDTExtensibility2 interface.
/// Receives notification that the Add-in is being unloaded.
/// </summary>
/// <param term='disconnectMode'>
/// Describes how the Add-in is being unloaded.
/// </param>
/// <param term='custom'>
/// Array of parameters that are host application specific.
/// </param>
/// <seealso class='IDTExtensibility2' />


public void OnDisconnection(Extensibility.ext_DisconnectMode
disconnectMode, ref System.Array custom)
{

_writer.writeLine ("Got the disconnection message");
if (_inspectors != null)
{
Marshal.ReleaseComObject (_inspectors);
}
Marshal.ReleaseComObject (_addInInstance);
Marshal.ReleaseComObject (_outlookApp);
}

/// <summary>
/// Implements the OnAddInsUpdate method of the
IDTExtensibility2 interface.
/// Receives notification that the collection of Add-ins has
changed.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host application specific.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnAddInsUpdate(ref System.Array custom)
{
_writer.writeLine ("Got the OnAddInsUpdate message");
}

/// <summary>
/// Keep around a copy of the inspectors collection. Since this is
created when we look at the
/// _outlookApp.Inspectors field, if we don't store it, it will be
eventually deleted by the
/// .NET garbage collector... unless we hold onto it.
/// </summary>
Outlook.Inspectors _inspectors = null;

/// <summary>
/// Implements the OnStartupComplete method of the
IDTExtensibility2 interface.
/// Receives notification that the host application has
completed loading.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host application specific.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnStartupComplete(ref System.Array custom)
{
_writer.writeLine ("Got the OnStartupComplete Message.");

//
// Add a callback that notices when a new inspector shows up.
//

try
{
_writer.writeLine ("The current number of inspectors is: " +
_outlookApp.Inspectors.Count);
_writer.writeLine ("The current number of explorers is: " +
_outlookApp.Explorers.Count);

///
/// Save a referece to the inspectors so the object doesn't get
garbage collected.
///

_inspectors = _outlookApp.Inspectors;
_inspectors.NewInspector += new
Outlook.InspectorsEvents_NewInspectorEventHandler
(this.NewInspectorCallback);

_writer.writeLine ("Done adding the inspector callbacks.");
}
catch (System.Exception e)
{
_writer.writeLine ("Failed to add inspector callbacks: " +
e.Message + "stack dump: " + e.StackTrace);
}
}

/// <summary>
/// Make sure we have only one allocation of the callback. Probably
not required.
/// </summary>
private _CommandBarButtonEvents_ClickEventHandler _clickEventHandler
= null;

/// <summary>
/// A new inspector window has been opened. This guy is called
whenever a new window containing an address
/// or a contact window opens up. We take a look at the item it is
displaying. If it is a contact, then
/// we need to make sure our button is visible on the tool bar. If
it doesn't exist, we need to create it.
/// Also, make sure we are listening for it's click event!
/// </summary>
/// <param name="target"></param>
public void NewInspectorCallback (Outlook.Inspector target)
{
try
{
_writer.writeLine ("Got a new inspector callback message -- window
title:" + target.Caption);

//
// No matter what, we add out button here (if we've not done it
already). We then,
// later, figure out if it shoudl be visible.
//

Microsoft.Office.Core.CommandBar bar =
target.CommandBars["Standard"];

// TODO: Figure out how to use the FindControl call...
CommandBarButton sendButton = null;
for (int i = 0; i < bar.Controls.Count; i++)
{
if (bar.Controls[i+1].Caption == "Send to Cell"
&& bar.Controls[i+1].Type == MsoControlType.msoControlButton)
{
sendButton = (CommandBarButton) bar.Controls[i+1];
break;
}
}

if (sendButton == null)
{
_writer.writeLine ("Adding the send to cell button!");
sendButton = (CommandBarButton) bar.Controls.Add
(MsoControlType.msoControlButton, 1, 10, bar.Controls.Count+1, false);
sendButton.Caption = "Send to Cell";
}

///
/// Every instance of the button needs a new delegate for the
callback. This is odd, because the
/// lifetime of the button itself seems to be different than the
callback. THe button will remain
/// around from inspector window to inspector window (indeed,
between invokations of outlook), but
/// the event linkage is just on an inspector window basis. This
is nice if you want to link different
/// event handlers depending upon the content type (calender vs
contact, etc.), but non-intuitive
/// (if that is, indeed, what is going on here).
///

if (_clickEventHandler == null)
{
_clickEventHandler = new
_CommandBarButtonEvents_ClickEventHandler (this.sendToCellClick);
}
sendButton.Click += _clickEventHandler;

//
// Ok -- we are now ready to see if this guy should be visible!
//

bool shouldBeVisible = false;
if (target.CurrentItem != null)
{
if (target.CurrentItem is Outlook.ContactItem) {
shouldBeVisible = true;
_writer.writeLine (" --> We got a live one -- make our button
visible!");
}
}

sendButton.Visible = shouldBeVisible;
}
catch (System.Exception e)
{
_writer.writeLine ("Inspector callback error: " + e.Message);
_writer.writeLine (" Stack Dump: " + e.StackTrace);
}

}

/// <summary>
/// When the user clicks our "send to cell" button, this guy gets
called. We have to put up the UI
/// to let the user select how to get it out to the cell phone...
/// </summary>
/// <param name="target"></param>
/// <param name="cancelByDefault"></param>
public void sendToCellClick (CommandBarButton target, ref bool
cancelByDefault)
{
try
{

_writer.writeLine ("User clicked on our button!");
_writer.writeLine ("Cancel by default is " +
cancelByDefault.ToString());

_writer.writeLine ("Done!");
}
catch (System.Exception e)
{
_writer.writeLine ("Failed to show dialog: " + e.Message);
_writer.writeLine ("Stack dump: " + e.StackTrace);
}
}

/// <summary>
/// Implements the OnBeginShutdown method of the
IDTExtensibility2 interface.
/// Receives notification that the host application is being
unloaded.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host application specific.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnBeginShutdown(ref System.Array custom)
{
_writer.writeLine ("Got the begin shutdown message");
}

/// <summary>
/// Keep a copy of the basic outlook application.
/// </summary>
private Outlook.Application _outlookApp;

/// <summary>
/// Keep a copy of the addin instance.
/// </summary>
private object _addInInstance;
}
}


"Frank Murphy" <fra...@hotmail.com> wrote in message news:<OX12UBzjBHA.1540@tkmsftngp05>...

Mike Timms

unread,
Dec 28, 2001, 10:48:26 AM12/28/01
to
Gordon,

gwa...@phys.washington.edu (Gordon Watts) wrote in message news:<3b2256.011227...@posting.google.com>...

> - But, in the end, Mike, you got it, indirectly. It wasn't that I
> wasn't releasing the objects... it was that I never held onto the
> Inspectors object! Once I made that a member variable, I was able to
> survive the garbage collection! Very nice. I suspect this mistake
> comes from me not knowing (very well) the interface to outlook and its
> programming model. Is this something I would have had to do in C++, or
> is this unique to C#?

The requirement to hold onto certain Outlook objects for the duration
of the Outlook session is standard regardless of implementation
language - it's also true for Add-Ins written in VB6. The Outlook
Application object is only passed from Outlook on the OnConnection
method call at the start of the session, so you need to preserve it
for subsequent use. The Add-In is completely driven by events raised
by Outlook, so it's also necessary to preserve any Outlook objects for
which you have registered event handlers.

I've found Randy Byrne's books on Outlook development to be essential
sources for an in-depth understanding of building Outlook COM Add-Ins.
Both "Building Applications with Microsoft Outlook 2000 Technical
Reference" or the more recent "Building Applications with Microsoft
Outlook Version 2002" use VB6, but it's fairly straightforward to
translate the sample code to C# or VB.NET.

See http://www.slipstick.com/dev/comaddins.htm for a full list of
references and code samples.

- Mike

Mike Timms

unread,
Dec 28, 2001, 11:11:43 AM12/28/01
to
Frank,

Here's a working VB.NET sample that reports on NewInspector events.
I'm using the "WithEvents" syntax rather than "AddHandler". However,
currently Outlook hangs around after termination in memory if an event
is handled, although not if you just start Outlook and then terminate.
I'm taking a look at this problem at the moment.

- Mike

Option Explicit On

Imports Microsoft.Office.Core
imports Extensibility
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports Outlook


<GuidAttribute("A1F9930D-1F78-460E-9710-35B3E2ED0C72"),
ProgIdAttribute("MyAddin5.Connect")> _
Public Class Connect Implements Extensibility.IDTExtensibility2

Private m_olApp As Outlook.Application
Private m_olNS As Outlook.NameSpace
Private WithEvents m_olInspectors As Outlook.Inspectors


Public Sub OnBeginShutdown(ByRef custom As System.Array) Implements
Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub

Public Sub OnAddInsUpdate(ByRef custom As System.Array) Implements
Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub

Public Sub OnStartupComplete(ByRef custom As System.Array) Implements
Extensibility.IDTExtensibility2.OnStartupComplete
End Sub

Public Sub OnDisconnection(ByVal RemoveMode As
Extensibility.ext_DisconnectMode, ByRef custom As System.Array)
Implements Extensibility.IDTExtensibility2.OnDisconnection
Marshal.ReleaseComObject(m_olApp)
Marshal.ReleaseComObject(m_olNS)
Marshal.ReleaseComObject(m_olInspectors)
End Sub

Public 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
m_olApp = CType(application, Outlook.Application)
m_olNS = m_olApp.GetNamespace("MAPI")
m_olInspectors = m_olApp.Inspectors

End Sub

Private Sub OnNewInspector(ByVal inspector As Outlook.Inspector)
Handles m_olInspectors.NewInspector

Dim objMailItem As Outlook.MailItem
Dim objPostItem As Outlook.PostItem
Dim sMessageClass As String = ""

Try
If TypeOf inspector.CurrentItem Is Outlook.MailItem Then
objMailItem = CType(inspector.CurrentItem,
Outlook.MailItem)
sMessageClass = objMailItem.MessageClass
ElseIf TypeOf inspector.CurrentItem Is Outlook.PostItem
Then
objPostItem = CType(inspector.CurrentItem,
Outlook.PostItem)
sMessageClass = objPostItem.MessageClass
End If
MessageBox.Show("New Inspector - " & sMessageClass)

Catch e As COMException
MessageBox.Show(e.ToString)
Catch
MessageBox.Show(Err.Number.ToString & " - " &
Err.Description)
Finally
If Not objMailItem Is Nothing Then
Marshal.ReleaseComObject(objMailItem)
ElseIf Not objPostItem Is Nothing Then
Marshal.ReleaseComObject(objPostItem)
End If
End Try

End Sub

End Class


"Frank Murphy" <fra...@hotmail.com> wrote in message news:<efEF2K1jBHA.556@tkmsftngp02>...

Mike Timms

unread,
Dec 28, 2001, 2:49:57 PM12/28/01
to
Frank,

I fixed the Outlook "hang". Apparently, it's necessary to free the
inspector object passed into the OnNewInspector event handler. Just add
Marshal.ReleaseComObject(inspector) to the Finally code block.

- Mike

"Mike Timms" <mti...@sympatico.ca> wrote in message

news:e1d085c2.0112...@posting.google.com...

Mike Timms

unread,
Jan 2, 2002, 10:42:36 PM1/2/02
to

"Frank Murphy" <fra...@hotmail.com> wrote in message
news:efEF2K1jBHA.556@tkmsftngp02...

> I read those excellent posts and made the changes. When I call AddHandler
> in VB I still get the NoInterface error.
>
> Mike, do you have some code or sample app you can share that works? I
don't
> think I'm the only one having the issues with Outlook :)
>
> Appreciate it.
>
> Frank
>

There's a VB.NET sample at:

http://groups.google.com/groups?hl=en&threadm=e1d085c2.0112280811.1e9305e%40
posting.google.com&rnum=1&prev=/groups%3Fq%3Dfrank%2Bmurphy%26hl%3Den%26grou
p%3Dmicrosoft.public.dotnet.framework.interop%26rnum%3D1%26selm%3De1d085c2.0
112280811.1e9305e%2540posting.google.com


0 new messages