MS Office Forum / Outlook / Interop / May 2004
Problem developing COM Add-In
|
|
Thread rating:  |
Richard Arthur - 20 May 2004 18:36 GMT 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/od c_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
Ron Cicotte - 20 May 2004 22:55 GMT 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
> 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 [quoted text clipped - 11 lines] > 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/od c_comshim.asp
> http://support.microsoft.com/default.aspx?scid=kb;EN-US;322027 > [quoted text clipped - 3 lines] > > Richard Arthur Richard Arthur - 21 May 2004 01:54 GMT 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
> Richard, > [quoted text clipped - 103 lines] > 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/od c_comshim.asp
> > http://support.microsoft.com/default.aspx?scid=kb;EN-US;322027 > > [quoted text clipped - 3 lines] > > > > Richard Arthur Richard Arthur - 21 May 2004 05:44 GMT 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
> Richard, > [quoted text clipped - 103 lines] > 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/od c_comshim.asp
> > http://support.microsoft.com/default.aspx?scid=kb;EN-US;322027 > > [quoted text clipped - 3 lines] > > > > Richard Arthur Ron Cicotte - 21 May 2004 13:56 GMT 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?
> I figured out why we are not getting subsequent events. > [quoted text clipped - 7 lines] > inspectors = Connect.applicationObject.Inspectors; > inspectors.NewInspector +=new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(I
> nspectors_NewInspector); > [quoted text clipped - 123 lines] > > 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/od c_comshim.asp
> > > http://support.microsoft.com/default.aspx?scid=kb;EN-US;322027 > > > [quoted text clipped - 3 lines] > > > > > > Richard Arthur Richard Arthur - 21 May 2004 16:23 GMT 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
> Thanks Richard! I think I'm on the right track here but I'm still not clear > on your implementation. [quoted text clipped - 14 lines] > the part of your code that implements the event handlers for the inspector > and AddItem events? Ron Cicotte - 21 May 2004 18:38 GMT 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
> Thanks Richard! I think I'm on the right track here but I'm still not clear > on your implementation. [quoted text clipped - 14 lines] > the part of your code that implements the event handlers for the inspector > and AddItem events? Richard Arthur - 21 May 2004 19:57 GMT 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
> Richard, > [quoted text clipped - 15 lines] > > Ron Ron Cicotte - 21 May 2004 20:51 GMT 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 ron.cicotte@summerstreet.net if you want to move this out of the group
-ron
> 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 [quoted text clipped - 57 lines] > > > > Ron
|
|
|