Problem developing COM Add-In

R

Richard Arthur

I am trying to develop a COM Add-In using visual C#. Many things were
working fine until I accidentally said No to the warning message about it
acessing my inbox. Now I no longer get warnings. I get events for certain
things once (My Command Bar Button will only raise the click event once, and
then never again). I am an individual developer, and do not have an
Exchange Server to work with. I am not using the Shim, either (I am having
trouble debugging when I slip the Shim in there).

How do I get my events back so that I can debug again?

How do I design this code so that I can install it on an Outlook client that
has not Exchange Server so that they do not get warnings either?

I've been looking at these articles, trying to understand what is going on,
and trying to fix it:

http://support.microsoft.com/default.aspx?scid=kb;en-us;327657&Product=ol2002
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnoxpta/html/odc_comshim.asp
http://support.microsoft.com/default.aspx?scid=kb;EN-US;322027

along with many of the articles they link to.

Thank you,

Richard Arthur
 
R

Ron Cicotte

Richard,

I ran into a similar problem a few weeks ago while developing a c# add-in
for Outlook 2003. In my case I am loading the com at application load and
the trust relationship is established because I am loading it on startup
and associating it with the main application object as noted in the first
article you reference. If you are attempting to create a com object that
will be managed by the Outlook application that may be your problem.
Remember that the .NET component is managed by the CLR while Outlook is
unmanaged code which is not.

The trust issue is spelled out in one of the links you provided (kb -
327657 )
When you use the Outlook object model in the COM add-in, only the main
Application object that is passed to the OnConnection event is trusted. If
you create a new Application object, by using the CreateObject method that
object and any of its subordinate objects, properties, and methods are not
trusted.
Though I did not have a problem loading my add-in I did have a problem
debugging it after replying "No" to a different question. This is the
warning that pops up when you are debugging and then close outlook from the
application rather than the debugger. If you then attempt to open outlook
outside of the debugger you will get an error message saying that a
catastophic error was encountered while attempting to load the add-in and
asks you if you want to disable it. If you say yes and disable the add-in
then you will not be able to debug the object or any other COM Add-in until
you re-enable and uninstall the Add-In. A "resiliency" key is set in the
registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\Outlook\Addins.
It may actually be in the same folder under HKEY_CURRENT_USER if you chose
to install only for yourself and not for everyone during setup.

In any case I spent quite a bit of time resolving this issue and thought
from your description that your problem may be related. I am using Outlook
2003 so the behaviour could be somewhat different in your case. If you
configure the Add-In to load at application startup your OnConnection code
should have the connect mode set as follows:

public void OnConnection(object application, Extensibility.ext_ConnectMode
connectMode, object addInInst, ref System.Array custom)
{
applicationObject = (Outlook.Application)application;
addInInstance = addInInst;
if(connectMode != Extensibility.ext_ConnectMode.ext_cm_UISetup)
(
OnStartupComplete(ref custom);
}
}

Doing this assures that you are connected to the Main application because
your "applicationObject is now pointing to the current Outlook.Application
passed to you at the time of connection. If you use the createObject method
to create an Outlook application object it won't be the Main application and
it will not be trusted.

Another issue I ran into that is similar to what you describe is the firing
of the events the first time and then nothing. This is happening on the
NewMail event for me. I'm looking for emails from a particular user with a
particular subject and want to do some processing when an email with the
expected criteria arrives. The NewMail event is instantiated in the
OnStartUpComplete method as follows:

public void OnStartupComplete(ref System.Array custom)
{
// First obtain the MAPI namespace from the Outlook Application
Outlook.NameSpace ns = applicationObject.GetNamespace("MAPI");
// add the New Mail event handler
try
{
ns.Application.NewMail += new
Outlook.ApplicationEvents_11_NewMailEventHandler(this.ssMail);
//ns.Application.NewMailEx += new
Outlook.ApplicationEvents_11_NewMailExEventHandler(this.NewMail);
}
catch (System.Exception ex)
{
u.logErr(ex.Message,"ssProcessOrders Addin
Error","ssProcessOrders.dll","OnStartUpCompleted");
}
}

the ssMail method is called the first time that the application receives new
mail messages after startup but is not called on subsequent events. I
haven't been able to figure this one out so far so I will be very interested
to know if you get a response to this

-ron cicotte
 
R

Richard Arthur

Ron,

Thank you for your suggestions. I did not find the "resiliency" key. That
is probably related to if Outlook decides to stop that COM Add-In.

I am trying to track down all the places that I am using unsafe accessing of
Outlook objects (using the objects passed in instead of the Application
object I originally received). Hopefully this will clear things up, but
we'll see. I'll let you know what I find, but hopefully someone else knows
a solution and can give us an answer.

Richard
 
R

Richard Arthur

I figured out why we are not getting subsequent events.

I tried re-registering for the event at the end of the NewInspector handling
method. When I did that, I got an exception about not being able to use an
object that has already been released from its RCW. I then realized that
the runtime had released the Inspectors collection, which had cleaned up the
associated events. So, now my code looks like this in the OnConnect
routine:

inspectors = Connect.applicationObject.Inspectors;
inspectors.NewInspector +=new
Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(I
nspectors_NewInspector);

And inside the Connect class, I have a variable declared as this:

private Outlook.Inspectors inspectors;

Now I catch the event every time. I hope this fix works for you, too.

Richard
 
R

Ron Cicotte

Thanks Richard! I think I'm on the right track here but I'm still not clear
on your implementation.
As I mentioned in my previous post I'm loading the the Add-in on application
startup so my OnConnection code looks like this:

applicationObject = (Outlook.Application)application;
addInInstance = addInInst;
if(connectMode != Extensibility.ext_ConnectMode.ext_cm_UISetup)
{
OnStartupComplete(ref custom);
}

So I'm assuming that the code for the new inspector event handler will be
in the OnStartupComplete method. Does the new ItemAdd event handler then
get instantiated in the new inspector code? I'm new to this object model
and it seems pretty strange to me but I'm willing to learn. Can you post
the part of your code that implements the event handlers for the inspector
and AddItem events?
 
R

Richard Arthur

Ron,

Here is my OnConnect Method.

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
try
{
Connect.applicationObject = (Outlook.Application)application;
Connect.addInInstance = addInInst;
//Remember the group of explorers
Connect.exps = applicationObject.Explorers;
//Register to be notified when a new explorer is added
Connect.Exps.NewExplorer += new Outlook.ExplorersEvents_NewExplorerEventHandler(NewExplorerMethod);
//Register to be notified when a new inspector is added
inspectors = Connect.applicationObject.Inspectors;
inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
Notice that I do not call OnStartupComplete in this routine. Outlook calls that method after all the Add-ins that it is loading have had OnConnect called and returned. I do some other initialization in that routine that caused problems if initialized twice. In my class defiinition I have the following code:
private static Outlook.Application applicationObject;
/// <summary>Gets the Outlook Application that was passed into this Add-In.</summary>
public static Outlook.Application ApplicationObject
{
get{return applicationObject;}
}
/// <summary>The instance of this add-in, as passed-in during startup.</summary>
private static object addInInstance;
#region Explorers code
private static Outlook.Explorers exps;
/// <summary>Gets the explorers available in Outlook.</summary>
public static Outlook.Explorers Exps
{
get{return exps;}
}
private void NewExplorerMethod(Outlook.Explorer newExplorer)
{
try
{
//Watch that explorer, and track it well...
Outlook.Explorer realNewExplorer = Connect.ApplicationObject.Explorers[Connect.ApplicationObject.Explorers.Count];
Wrappers.ExplorerWrapper wrapper = new Talisman.Wrappers.ExplorerWrapper(realNewExplorer);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
#endregion
#region Inspectors Code
private Outlook.Inspectors inspectors;
private void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
try
{
Debug.WriteLine("New Inspector!");
//TODO: get the item for this inspector, and attach events to it...
Outlook.Inspector temp = inspectors[applicationObject.Inspectors.Count];
object currentItem = temp.CurrentItem;
if(currentItem is Outlook.MailItem)
{
Outlook.MailItem currMailItem = (Outlook.MailItem)currentItem;
if(!currMailItem.Sent)
{ //This is an active email that is waiting to be sent. Watch the form and the MailItem
EmailWrapper tempEmail = new EmailWrapper(currMailItem);
InspectorWrapper tempInspector = new InspectorWrapper(temp);
}
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
#endregion
So "Inspectors_NewInspector" is the routine that I have that gets called whenever a new Inspector is added to the Inspectors collection (by the way, this is where I tried to re-register for the event and got the exception). I also keep a reference to the Inspectors collection ("private Outlook.Inspectors inspectors") so that it does not get garbage collected by the runtime. I was not doing this before, and so referencing the collection once through "Application.Inspectors" caused it to be created, and then later collected because the runtime saw that it is no longer being used. Storing it in a class-level variable like this keeps the runtime from ever cleaning it up.

Here is my InspectorWrapper class that I use to keep a reference to an Inspector as long as it is running:

public class InspectorWrapper
{
#region Static Members
private static ArrayList watchingItems;
static InspectorWrapper()
{
watchingItems = new ArrayList();
}
#endregion
public InspectorWrapper(Outlook.Inspector inspector)
{
this.inspector = inspector;
((Outlook.InspectorEvents_Event)this.inspector).Close += new Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(Inspector_Close);
watchingItems.Add(this);
}
private Outlook.Inspector inspector;
public Outlook.Inspector Inspector
{get{return this.inspector;}}
private void Inspector_Close()
{ //Clean up this watcher
((Outlook.InspectorEvents_Event)this.inspector).Close -= new Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(Inspector_Close);
watchingItems.Remove(this);
}
}
Inside this class, I attach an event handler to the Inspector that is passed into the constructor. I'm just listening for the close event so that I can clean up this object, and close it down. Eventually I will be listening for other events, and possibly raising my own in those routines, but this class is at step along the way to that point. I also have a static member on the class ("watchingItems") keeping track of all the open Inspectors. Since I do not need to access them anywhere else, just in this Wrapper class, I am just keeping a reference to these objects inside this static member.

I suspect that in your case, your OnStartUpcomplete code should look something more like this:

public void OnStartupComplete(ref System.Array custom)
{
// add the New Mail event handler
try
{
applicationObject.NewMail += new Outlook.ApplicationEvents_11_NewMailEventHandler(this.ssMail);
}
catch (System.Exception ex)
{
u.logErr(ex.Message,"ssProcessOrders Addin Error","ssProcessOrders.dll","OnStartUpCompleted");
}
}

In this case, you are directly attaching to the applicationObject that was passed into the OnConnect method of the class. Accessing the Application member of the objects you are using probably will result in using "non-trusted" code. And since, in this case, you have a reference to the trusted application object, you will not run into problems. All objects in Outlook have an "Application" property, but I suspect that that property always returns an "untrusted" reference to the application object, whereas the only trusted instance is the one passed into the OnConnect method. You should always access the trusted Application object directly instead of through any other objects in the Outlook model. My second code block defined a public static "ApplicationObject" property, that I use to allow any other class in my Add-In to access the Outlook Application directly.

I hope this helps. It has certainly helped me to understand what is going on.

Richard
 
R

Ron Cicotte

Richard,

Thanks very much for your example. I have been looking for weeks trying to
figure out how to implement event handlers. I'm writing a simple app that
monitors incoming mail messages and then need to do some processing when I
get one from a particular organization with a specific subject. I have it
implemented with a button at the moment but need to make it automatic and
that's where I ran into problems. I'll take a look at your example and see
if I can apply it.

I've found it very difficult to find good examples in C# . Most everything
for outlook is in VB and while I can follow most of it when the subject
turns to event handling and other issues that involve passing objects the
translation is not so obvious. Do you have any other references you've
found useful?

Thanks again for sharing your code samples. Examples are always helpful.

Ron


Ron,

Here is my OnConnect Method.

public void OnConnection(object application, Extensibility.ext_ConnectMode
connectMode, object addInInst, ref System.Array custom)
{
try
{
Connect.applicationObject = (Outlook.Application)application;
Connect.addInInstance = addInInst;
//Remember the group of explorers
Connect.exps = applicationObject.Explorers;
//Register to be notified when a new explorer is added
Connect.Exps.NewExplorer += new
Outlook.ExplorersEvents_NewExplorerEventHandler(NewExplorerMethod);
//Register to be notified when a new inspector is added
inspectors = Connect.applicationObject.Inspectors;
inspectors.NewInspector += new
Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(I
nspectors_NewInspector);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
Notice that I do not call OnStartupComplete in this routine. Outlook calls
that method after all the Add-ins that it is loading have had OnConnect
called and returned. I do some other initialization in that routine that
caused problems if initialized twice. In my class defiinition I have the
following code:
private static Outlook.Application applicationObject;
/// <summary>Gets the Outlook Application that was passed into this
Add-In.</summary>
public static Outlook.Application ApplicationObject
{
get{return applicationObject;}
}
/// <summary>The instance of this add-in, as passed-in during
startup.</summary>
private static object addInInstance;
#region Explorers code
private static Outlook.Explorers exps;
/// <summary>Gets the explorers available in Outlook.</summary>
public static Outlook.Explorers Exps
{
get{return exps;}
}
private void NewExplorerMethod(Outlook.Explorer newExplorer)
{
try
{
//Watch that explorer, and track it well...
Outlook.Explorer realNewExplorer =
Connect.ApplicationObject.Explorers[Connect.ApplicationObject.Explorers.Coun
t];
Wrappers.ExplorerWrapper wrapper = new
Talisman.Wrappers.ExplorerWrapper(realNewExplorer);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
#endregion
#region Inspectors Code
private Outlook.Inspectors inspectors;
private void
Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector
Inspector)
{
try
{
Debug.WriteLine("New Inspector!");
//TODO: get the item for this inspector, and attach events to it...
Outlook.Inspector temp =
inspectors[applicationObject.Inspectors.Count];
object currentItem = temp.CurrentItem;
if(currentItem is Outlook.MailItem)
{
Outlook.MailItem currMailItem = (Outlook.MailItem)currentItem;
if(!currMailItem.Sent)
{ //This is an active email that is waiting to be sent. Watch
the form and the MailItem
EmailWrapper tempEmail = new EmailWrapper(currMailItem);
InspectorWrapper tempInspector = new InspectorWrapper(temp);
}
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
#endregion
So "Inspectors_NewInspector" is the routine that I have that gets called
whenever a new Inspector is added to the Inspectors collection (by the way,
this is where I tried to re-register for the event and got the exception).
I also keep a reference to the Inspectors collection ("private
Outlook.Inspectors inspectors") so that it does not get garbage collected by
the runtime. I was not doing this before, and so referencing the collection
once through "Application.Inspectors" caused it to be created, and then
later collected because the runtime saw that it is no longer being used.
Storing it in a class-level variable like this keeps the runtime from ever
cleaning it up.
Here is my InspectorWrapper class that I use to keep a reference to an
Inspector as long as it is running:
public class InspectorWrapper
{
#region Static Members
private static ArrayList watchingItems;
static InspectorWrapper()
{
watchingItems = new ArrayList();
}
#endregion
public InspectorWrapper(Outlook.Inspector inspector)
{
this.inspector = inspector;
((Outlook.InspectorEvents_Event)this.inspector).Close += new
Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(Inspector
_Close);
watchingItems.Add(this);
}
private Outlook.Inspector inspector;
public Outlook.Inspector Inspector
{get{return this.inspector;}}
private void Inspector_Close()
{ //Clean up this watcher
((Outlook.InspectorEvents_Event)this.inspector).Close -= new
Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(Inspector
_Close);
watchingItems.Remove(this);
}
}
Inside this class, I attach an event handler to the Inspector that is passed
into the constructor. I'm just listening for the close event so that I can
clean up this object, and close it down. Eventually I will be listening for
other events, and possibly raising my own in those routines, but this class
is at step along the way to that point. I also have a static member on the
class ("watchingItems") keeping track of all the open Inspectors. Since I
do not need to access them anywhere else, just in this Wrapper class, I am
just keeping a reference to these objects inside this static member.
I suspect that in your case, your OnStartUpcomplete code should look
something more like this:
public void OnStartupComplete(ref System.Array custom)
{
// add the New Mail event handler
try
{
applicationObject.NewMail += new
Outlook.ApplicationEvents_11_NewMailEventHandler(this.ssMail);
}
catch (System.Exception ex)
{
u.logErr(ex.Message,"ssProcessOrders Addin
Error","ssProcessOrders.dll","OnStartUpCompleted");
}
}
In this case, you are directly attaching to the applicationObject that was
passed into the OnConnect method of the class. Accessing the Application
member of the objects you are using probably will result in using
"non-trusted" code. And since, in this case, you have a reference to the
trusted application object, you will not run into problems. All objects in
Outlook have an "Application" property, but I suspect that that property
always returns an "untrusted" reference to the application object, whereas
the only trusted instance is the one passed into the OnConnect method. You
should always access the trusted Application object directly instead of
through any other objects in the Outlook model. My second code block
defined a public static "ApplicationObject" property, that I use to allow
any other class in my Add-In to access the Outlook Application directly.
I hope this helps. It has certainly helped me to understand what is going
on.
Richard
 
R

Richard Arthur

Well, I hope this works better for you. I've known how to use VB for years,
so I know how to read it and translate it to C#. I've also spent a lot of
time looking inside the source code that the designer generates in C# to
figure out how the designer attaches to an object's events (there is a
#region called "Component Designer generated code" in all forms where a
method called "InitializeComponent" is defined).

VB, when you declare an object as "WithEvents" it automatically generates
the code to attach to the events that you are listening to. With C#, we
have to explicitly attach to those events.

I also suggest that you look at System.Threading.Thread to learn how to use
delegates and understand them better. I even have a nice tutorial that I
have put together on http://www.startether.com/BYUDotNet . I'm running a
news group, but it is summer time, so all the users are gone, and I have not
changed it for a while. But there is still a lot of good information there.

I don't know how long you have been using C#, but that site I just
referenced is mostly for beginners. If you are mostly having trouble with
Outlook automation, then we are prettymuch in the same boat. I've done
automation in the past, mostly with VB, and have had a much easier time
doing Automation. But I have never tried to do anything too advanced. This
is my first attempt at creating an Add-In, so I am learning a lot of new
things, too.

I've also been thinking about how you were attaching to the "NewMail" event.
You may be getting a trusted version of the Application object, but you were
getting a different pointer to it. I bet that if you do this:

Debug.WriteLine(ns.Application == applicationObject);

You will get a "false". In which case, you could stay attached to the event
by keeping a pointer to the object returned by ns.Application the first time
you access it.

Good luck,

Richard
 
R

Ron Cicotte

Yeah! I thought about grabbing the pointer to the application object but
thought that it should be equivalent since the assignment should return a
pointer to the object and not a copy of it. It appears that is not the
case. (ns.Appication == since applicationObject) indeed returns false. I am
a slightly over the hill C/C++ programmer (I started coding in C when it was
a brand new language in 1982). I've been using C# since .NET first appeared
so am pretty comfortable with it.

I'm afraid I've been a bit of a VB bigot. My best friend does most of his
coding in VB and we have arguements about it all the time. Until .NET I
think I had good reason to avoid it but now I'm not so sure. We are both
more database gurus than programmers these days. I just finished a gig
where I re-designed the architecture of a company moving from DB2 running on
AS400's to SQL Server 2000 running on 8 way intel processor machines. The
staff are all Progress and Cobol programmers and it was a real challenge
getting them to understand that cursors are not the best way to look at
data.

I have a site under construction at http;//summerstreet.com. I'm building a
blog there that will let me communicate more effectively. I expect it to be
ready in a couple of weeks. I have an rss news reader component that I've
developed there. It's written in C# and I'll be using it to demonstrate how
to build components. My problem at the moment is trying to learn the
Outlook Object model. It seems a bit funky to me. I guess it's because it
was written before .NET and there are all those compatibility issues between
managed and unmanaged code.

I'm fairly familiar with delegates having built some web services but will
be interested in looking at your tutorial.
..
Thanks again for your help on this. I've been searching for quite a while
now. I'll let you know how I make out and will make sure you get credit
when my component is finished. You can contact me directly at
(e-mail address removed) if you want to move this out of the group

-ron
 

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