IDTExtensibility2 based addin Will Not Unload if System.Timer Used

P

Philip Enny

Any help with this issue would be greatly appreciated. In creating an Outlook
Addin, via an IDTExtensibility2 implementation, if I access the
ActiveInspector function of the Outlook application object from a
System.Timer, Outlook will not close and will remain in memory. Consider the
following addin:



using System;

using Extensibility;

using System.Runtime.InteropServices;

using System.Timers;

using System.Diagnostics;

using Microsoft.Office.Interop.Outlook;

using System.Reflection;

using System.Text;

using System.Windows.Forms;

namespace OutlookTestExtensibilityAddin

{

#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
OutlookTestExtensibilityAddinSetup project,

// right click the project in the Solution Explorer, then choose install.

#endregion


/// <summary>

/// The object for implementing an Add-in.

/// </summary>

/// <seealso class='IDTExtensibility2' />

[GuidAttribute("B2D209D5-78F3-4DCF-AFE4-92C913B35373"),
ProgId("OutlookTestExtensibilityAddin.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

/// <summary>

/// Implements the constructor for the Add-in object.

/// Place your initialization code within this method. ///

/// </summary>

///

Microsoft.Office.Interop.Outlook.Application outlookApp;

System.Timers.Timer sampleTimer;

DateTime previousDateTime;


public Connect()

{

InitializeSystemTimer();

Debug.WriteLine("Connect ctor called...");

}

private void InitializeSystemTimer()

{

sampleTimer = new System.Timers.Timer();

sampleTimer.Elapsed += sampleTimer_Elapsed;

sampleTimer.Interval = 250;

}

int InOutCount = 0;

private void TimerElapsed()

{

Debug.WriteLine("In Count: " + ++InOutCount);

TimeSpan ts = DateTime.Now.Subtract(previousDateTime);

previousDateTime = DateTime.Now;

Debug.WriteLine("Timespan: " + ts.Milliseconds + " mSecs");

Inspector inspector = null;

try

{

inspector = outlookApp.ActiveInspector();

}

finally

{

inspector = null;

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

GC.WaitForPendingFinalizers();

}

Debug.WriteLine("Out Count: " + --InOutCount);

}

void sampleTimer_Elapsed(object sender, ElapsedEventArgs e)

{

TimerElapsed();

}

/// <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)

{

Debug.WriteLine("OnConnection called...");

outlookApp = (Microsoft.Office.Interop.Outlook.Application)application;

ApplicationEvents_11_Event events = outlookApp;

events.Quit += new ApplicationEvents_11_QuitEventHandler(events_Quit);

}

void events_Quit()

{

sampleTimer.Stop();

}

/// <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)

{

Debug.WriteLine("OnDisconnection called...");

}



/// <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)

{

Debug.WriteLine("OnAddInsUpdate called...");

}

/// <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)

{

Debug.WriteLine("OnStartupComplete called...");

sampleTimer.Start();

previousDateTime = DateTime.Now;

}

/// <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)

{

Debug.WriteLine("OnBeginShutdown called...");

sampleTimer.Stop();

sampleTimer.Dispose();

}

}

}





Outlook 2003 will not unload if this addin is loaded. I need to have a timer
(or a seperate thread) to poll the state of an external device and take
action only if an ActiveInspector exists. Greatly appreciate any help on
this. I've tried Marshal.ReleaseComObject also to no avail.

Another suggestion or observation was that I was not releasing the
application object in OnDisconnection or OnBeginShutdown. However, neither of
these are being called as the call to ActiveInspector is causing reference to
an Inspector object. If I use a System.Windows.Form.Timer everything works
fine but because there is some time spent checking the external device, the
UI freezes.
 
K

Ken Slovak - [MVP - Outlook]

Outlook 2003 has a bug where if you have any Outlook objects instantiated
and not released then OnDisconnection() will not fire.
Outlook.Application.Quit() is of no use to you since by the time it fires
all Outlook objects are out of scope already.

The OnDisconnection() bug was fixed in Outlook 2007.

To work around that bug the most common thing is to handle the
Explorer.Close() event. If in that event there are no other Explorers and
there are no Inspectors you call your teardown procedure to release all
Outlook objects and then and only then OnDisconnection() will fire.

Never, ever access the Outlook object model from another thread other than
the main thread of your addin. Outlook isn't set up for handling calls like
that from other threads and will crash or hang unless you carefully marshal
any object model calls from the alternate thread back to your main thread
before making the object model call.

I would make 2 modifications to the timer event handling code. First I'd
check first for (Inspectors.Count > 0) before trying to instantiate an
Inspector object from ActiveInspector(), second unless you are leaving
something out why are you handling Inspectors at all in the timer elapsed
event?

Since Quit() is too late to shut off the timer you want to move that call
into your Explorer.Close() event handler.

I'd also set a boolean flag in that event handler in case the Explorer is
closing while the timer elapsed handler is in process. Then I'd have the
timer process check that flag a few times during the event handler. I'd also
disable the timer in the handler and re-enable it at the end of the handler
if the flag wasn't set.




Philip Enny said:
Any help with this issue would be greatly appreciated. In creating an
Outlook
Addin, via an IDTExtensibility2 implementation, if I access the
ActiveInspector function of the Outlook application object from a
System.Timer, Outlook will not close and will remain in memory. Consider
the
following addin:



using System;

using Extensibility;

using System.Runtime.InteropServices;

using System.Timers;

using System.Diagnostics;

using Microsoft.Office.Interop.Outlook;

using System.Reflection;

using System.Text;

using System.Windows.Forms;

namespace OutlookTestExtensibilityAddin

{

#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
OutlookTestExtensibilityAddinSetup project,

// right click the project in the Solution Explorer, then choose install.

#endregion


/// <summary>

/// The object for implementing an Add-in.

/// </summary>

/// <seealso class='IDTExtensibility2' />

[GuidAttribute("B2D209D5-78F3-4DCF-AFE4-92C913B35373"),
ProgId("OutlookTestExtensibilityAddin.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

/// <summary>

/// Implements the constructor for the Add-in object.

/// Place your initialization code within this method. ///

/// </summary>

///

Microsoft.Office.Interop.Outlook.Application outlookApp;

System.Timers.Timer sampleTimer;

DateTime previousDateTime;


public Connect()

{

InitializeSystemTimer();

Debug.WriteLine("Connect ctor called...");

}

private void InitializeSystemTimer()

{

sampleTimer = new System.Timers.Timer();

sampleTimer.Elapsed += sampleTimer_Elapsed;

sampleTimer.Interval = 250;

}

int InOutCount = 0;

private void TimerElapsed()

{

Debug.WriteLine("In Count: " + ++InOutCount);

TimeSpan ts = DateTime.Now.Subtract(previousDateTime);

previousDateTime = DateTime.Now;

Debug.WriteLine("Timespan: " + ts.Milliseconds + " mSecs");

Inspector inspector = null;

try

{

inspector = outlookApp.ActiveInspector();

}

finally

{

inspector = null;

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

GC.WaitForPendingFinalizers();

}

Debug.WriteLine("Out Count: " + --InOutCount);

}

void sampleTimer_Elapsed(object sender, ElapsedEventArgs e)

{

TimerElapsed();

}

/// <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)

{

Debug.WriteLine("OnConnection called...");

outlookApp = (Microsoft.Office.Interop.Outlook.Application)application;

ApplicationEvents_11_Event events = outlookApp;

events.Quit += new ApplicationEvents_11_QuitEventHandler(events_Quit);

}

void events_Quit()

{

sampleTimer.Stop();

}

/// <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)

{

Debug.WriteLine("OnDisconnection called...");

}



/// <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)

{

Debug.WriteLine("OnAddInsUpdate called...");

}

/// <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)

{

Debug.WriteLine("OnStartupComplete called...");

sampleTimer.Start();

previousDateTime = DateTime.Now;

}

/// <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)

{

Debug.WriteLine("OnBeginShutdown called...");

sampleTimer.Stop();

sampleTimer.Dispose();

}

}

}





Outlook 2003 will not unload if this addin is loaded. I need to have a
timer
(or a seperate thread) to poll the state of an external device and take
action only if an ActiveInspector exists. Greatly appreciate any help on
this. I've tried Marshal.ReleaseComObject also to no avail.

Another suggestion or observation was that I was not releasing the
application object in OnDisconnection or OnBeginShutdown. However, neither
of
these are being called as the call to ActiveInspector is causing reference
to
an Inspector object. If I use a System.Windows.Form.Timer everything works
fine but because there is some time spent checking the external device,
the
UI freezes.
 
P

Philip Enny

Ken,
Thanks for the info. I need to check the ActiveInspector in the Timer event
as I need to know whether any Inspector is Active as I only want to perform a
particular action if an Inspector is Active (say in this case check the state
of a peripheral device.) I am under the impression, perhaps incorrectly, that
although an Inspector window may be open, the Inspector window itself may not
be active (focused). So although I may have more than 0 Inspector windows
open, I may have no Active Inspector windows. This was the basis for my using
the ActiveInspector function. I can keep a list of inspector windows, but
that still does not help me as these Inspector objects do not have an Active
property. So, I guess I could use Win32 to determine if any inspector windows
are active (although each Inspector window class is different) it would be a
nuisance and not necessarily version compatible.

What is the technical reason for not being able to access the Outlook OM
from anything other than the main UI thread?
 
K

Ken Slovak - [MVP - Outlook]

You can use ActiveInspector, but first check if Inspectors.Count > 0.

It's because Outlook itself expects all calls from in-process applications
(addins) to be running on the main thread and doesn't handle it well when
calls to the object model aren't running on that thread. An example of doing
that were earlier versions of the Apple ITunes calendar synch addin that
called in on a different thread and crashed/hung Outlook very nicely.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top