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

TLBIMP-generated code doesn't do events right?

0 views
Skip to first unread message

Mike Morearty

unread,
Dec 17, 2001, 3:17:01 PM12/17/01
to
I'm having trouble getting events to work correctly with TLBImp'd
code. The problem, in a nutshell: TLBImp generates helper classes
called <eventinterfacename>_SinkHelper, but because those classes are
not public, COM cannot call QueryInterface(IID_IDispatch) on them. I
don't know if this should be considered a TLBImp bug or a .NET
framework bug, but it's definitely one or the other.

Gory details:

I've imported the Microsoft Outlook type library with:

tlbimp msoutl9.olb

This creates Outlook.dll. I then use this to write an Outlook COM
Add-In using C#. (My class implements _IDTExtensibility2, which I got
by importing "Microsoft Add-In Designer".)

In my implementation of _IDTExtensibility2.OnConnection(), I take the
Application which I am passed, and try to be notified of the ItemSend
event:

public void OnConnection(object Application, ...)
{
Outlook.Application app = (Outlook.Application) Application;

// OnItemSend is a function in my class
app.ItemSend += new
Outlook.ApplicationEvents_ItemSendEventHandler(OnItemSend);
}

However, this throws an exception. Upon digging into this, it turns
out that when the "+=" is executed, .NET takes the
Outlook.Application, gets an IConnectionPoint from it for the
ApplicationEvents interface, and calls IConnectionPoint::Advise,
passing in a pointer to an ApplicationEvents_SinkHelper.
ApplicationEvents_SinkHelper is a class generated by TlbImp, which
implements the ApplicationEvents interface.

But here's where the problem comes in. ApplicationEvents is a dual
interface (supports IDispatch). But when Outlook takes the pointer it
was passed and does QueryInterface(IID_IDispatch) on it, .NET returns
E_NOINTERFACE!

After experimentation, I eventually figured out that, because
ApplicationEvents_SinkHelper is not a "public" class, that seems to be
why .NET refuses to allow COM to get an IDispatch from it. If I use
ILDAsm to get an Outlook.il file, modify it so that
ApplicationEvents_SinkHelper is public, and then recompile it with
ILAsm, then the "+=" operator works.

Mike Timms

unread,
Dec 19, 2001, 8:01:19 AM12/19/01
to
Mike,

Thanks for this fine piece of detective work! I'd been struggling
with this same problem for a while. I rebuilt the full library
according to your prescription and it works fine for all the events I
have so far tested, and I now have a fully functional Outlook .NET COM
Add-in! The added benefit is no source code changes once MS have
finally fixed this thing.

One point to note was that the build process kept regenerating the
original Interop.Outlook.DLL. I found you could inhibit this
behaviour by right clicking on the Outlook entry in the project
References and setting CopyLocal = False.

- Mike


mi...@morearty.com (Mike Morearty) wrote in message news:<fe2a4468.01121...@posting.google.com>...

Gordon Watts

unread,
Dec 26, 2001, 1:25:05 AM12/26/01
to
Hi,
Many thanks for this message. It solved a problem I'd been banging
my head against for several days, and introduced me to a nice new
tool.

I now have a nice COM addin that correctly adds a button to the
command bar. I have several follow-on questions:

- In the NewInspector callback, how do you determine the class of the
"CurrentItem"? Right now I'm just casting it to be the type I want,
and if it fails, I decide it isn't. Works, but feels ugly. Most sample
code tells one to access the "Class" property of CurrentItem, but in
the dotnet outlook object model CurrentItem is just an object.

- In several cases the documents talk about optional parameters that
can be passed to outlook routines (for example, the
CommandBar.Controls.Add method). I can easily get around this by using
the defaults specified in the docs, and I supposed there is no way for
Interop to tell that these are optional arguments, but I wonder
none-the-less...

Again, many thanks for the help!

Cheers,
Gordon.

mti...@sympatico.ca (Mike Timms) wrote in message news:<e1d085c2.01121...@posting.google.com>...

Mike Timms

unread,
Dec 26, 2001, 8:57:38 PM12/26/01
to
Gordon,

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


> - In the NewInspector callback, how do you determine the class of the
> "CurrentItem"? Right now I'm just casting it to be the type I want,
> and if it fails, I decide it isn't. Works, but feels ugly. Most sample
> code tells one to access the "Class" property of CurrentItem, but in
> the dotnet outlook object model CurrentItem is just an object.

I've also been trying to find a cleaner approach to accessing the
common propeties of items without having to cast to the specific item
type.
You can check for a specific object type as in:

string messageClass;

if (inspector.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem mitem = inspector.CurrentItem as
Outlook.MailItem;
messageClass = mitem.MessageClass;
}
else if (inspector.CurrentItem is Outlook.PostItem)
{
Outlook.PostItem pitem = inspector.CurrentItem as
Outlook.PostItem;
messageClass = pitem.MessageClass;
}
else if ......

However, this doesn't help that much. The problem is that there is no
base item class which the other items can inherit from. The only
partial solution I could come up with was to create a pseudo item
class that wraps the ugliness of testing the object type and gives the
client code at least the illusion of a base item object. A partial
implementation is:

public class OutlookItem : IDisposable
{
private Outlook.OlItemType m_itemType;
private Outlook.PostItem m_pItem;
private Outlook.MailItem m_mItem;
....

public OutlookItem(object item)
{

if (item is Outlook.PostItem)
{
m_itemType = Outlook.OlItemType.olMailItem;
m_mItem = item as Outlook.MailItem;
}
else if (item is Outlook.PostItem)
{
m_itemType = Outlook.OlItemType.olPostItem;
m_pItem = item as Outlook.PostItem;
}
else if (item is ............)
.........
else
{
// raise invalid object exception
}
}
public Outlook.OlItemType ItemType
{
get
{ return m_itemType; }
}

public string MessageClass
{
get
{
switch (m_itemType)
{
case Outlook.OlItemType.olPostItem:
return m_pItem.MessageClass;
case Outlook.OlItemType.olMailItem:
return m_mItem.MessageClass;
case Outlook.OlItemType......
............
}
return "";
}
set
{
switch (m_itemType)
{
case Outlook.OlItemType.olPostItem:
m_pItem.MessageClass = value;
break;
case Outlook.OlItemType.olMailItem:
m_mItem.MessageClass = value;
break;
case Outlook.OlItemType......


}
}
}

public void Dispose ()
{
switch (m_itemType)
{
case Outlook.OlItemType.olPostItem:
Marshal.ReleaseComObject(m_pItem);
break;
case Outlook.OlItemType.olMailItem:
Marshal.ReleaseComObject(m_mItem);
break;
case Outlook.OlItemType......
}
}
}


This class can then be used as follows:

private void OnNewInspector(Outlook.Inspector inspector)
{
string messageClass;
OutlookItem item = new OutlookItem(inspector.CurrentItem);
messageClass = item.MessageClass;
if (messageClass = "IPM.Note.MyMessageClass")
{
..........
}
item.Dispose();
}

This does at least hide the ugliness in the helper class. However, I
assume there is potentially a way to use interop library functions to
provide a more elegant solution. Has anybody got an ideas?

>
> - In several cases the documents talk about optional parameters that
> can be passed to outlook routines (for example, the
> CommandBar.Controls.Add method). I can easily get around this by using
> the defaults specified in the docs, and I supposed there is no way for
> Interop to tell that these are optional arguments, but I wonder
> none-the-less...
>

I believe you use Missing.Value (from the System.Reflection Namespace)
for each missing optional parameter.

regards,

- Mike

Gordon Watts

unread,
Dec 27, 2001, 6:02:53 PM12/27/01
to
Hi,
The "is" operator is great. I'd not realized it was part of C#. This makes
the code much more readable that it was, thanks!

Cheers,
Gordon.
mti...@sympatico.ca (Mike Timms) wrote in message news:<e1d085c2.01122...@posting.google.com>...

Neema Moraveji

unread,
Dec 31, 2001, 11:35:13 AM12/31/01
to
> I've imported the Microsoft Outlook type library with:
>
> tlbimp msoutl9.olb

I also did this which produced an Outlook.DLL as well as an Office.DLL. I
copied them to my project's Debug directory and added references to those 2
DLL's in my VS.NET project. I also did this to produce
AddINDesignerObjects.DLL.

> This creates Outlook.dll. I then use this to write an Outlook COM
> Add-In using C#. (My class implements _IDTExtensibility2, which I got
> by importing "Microsoft Add-In Designer".)

I am trying to implement _IDTExtensibility2 (regular IDTExtensibility2
compiles but I can't get Outlook to recognize DLLs built using it) but when
I compile the app, it says the _IDTExtensibility interface cannot be found.
Do we need to implement IDTExtensibility2 or _IDTExtensibility2? What is
the difference? I am pretty sure I am missing some references or something,
could you please send some hints? In my VS.NET Solution Explorer, I have
the following references listed:

AddInDesignerObjects
envdte
Microsoft.Office.Core
MSORUNLib
Office
Outlook
stdole
System
System.Data etc.

Here is the top of my c# file:

namespace MyAddin1
{
using System;
using Outlook;
using EnvDTE;
using System.Runtime.InteropServices;
using Extensibility;
[GuidAttribute("885A2537-98A5-4C85-868E-29C219F50C5C"),
ProgId("MyAddin1.Connect")]
public class Connect : Object, _IDTExtensibility2 {
public Connect() // constructor
{
}
public void OnConnection(object application, ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
}

/* ... etc other IDTExtensibility2 functions overridden here */

}

I need to get an Outlook add-in working in C# for my professor... I've done
it successfully using VB6.

Thanks,
Neema


Mike Timms

unread,
Dec 31, 2001, 5:02:44 PM12/31/01
to
Neema,

For my C# Outlook Add-In I'm using VS.NET RC, Windows 2000 SP2, Outlook 2000
SR-1.

Here's how I set up:

1. In VS.NET build a new project selecting Other Projects\Extensibility
Projects\Shared Add-in as the project type.

2. a) In the Add-in Wizard select' Create an Add-in using Visual C#'.
b) Deselect all Application Host's except for Microsoft Outlook.
c) For Add-in Options, select 'I would like my Add-in to load when the
host application loads.'

3. In the Solution Explorer, add a reference to Microsoft Outlook Library
9.0.
Set the Copy Local property for this reference to False to inhibit
regeneration of the Outlook COM wrapper.

4. Rebuild the Outlook wrapper (Interop.Outlook.DLL) making the Sink Helper
methods public, and deposit the resulting DLL into the debug directory.

5. Under the Configuration properties\Debugging in the property pages for
the Addin, set the Start Application to the path for the Outlook executable.

6. Build the Add-in and then start a debug session.

namespace MyAddin1
{
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Office.Core;
using Extensibility;
using EnvDTE;
using Outlook;
using Office;
public class Connect : Object, Extensibility.IDTExtensibility2,
IDTCommandTarget

{

public Connect()
{
}

#endregion

#region Public Methods

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


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


public void OnAddInsUpdate(ref System.Array custom)

{

}

public void OnStartupComplete(ref System.Array custom)

{

}

public void OnBeginShutdown(ref System.Array custom)

{

}


public void QueryStatus(string commandName, EnvDTE.vsCommandStatusTextWanted
neededText, ref EnvDTE.vsCommandStatus status, ref object commandText)

{

}

public void Exec(string commandName, EnvDTE.vsCommandExecOption
executeOption, ref object varIn, ref object varOut, ref bool handled)
{
}

private object applicationObject = application;
private object addInInstance = addInInst;

}


"Neema Moraveji" <removet...@yahoo.com> wrote in message
news:OVW6vkhkBHA.2372@tkmsftngp05...

Message has been deleted

Mike Timms

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

>I have only verion 9.1 (listed as 10.0) of the object model DLL, so I tried
>it with that.

Yes, that's ok. I was using Outlook 2000 as opposed to Outlook 2002.


>> 4. Rebuild the Outlook wrapper (Interop.Outlook.DLL) making the Sink
Helper
>> methods public, and deposit the resulting DLL into the debug directory.

>How do you do this part? I don't understand how I am supposed to rebuild
>the DLL and make the Sink Helper methods public...

Assuming you're building a debug version of your application, adding the
reference to Outlook should give you a file called "Interop.Outlook.DLL" in
the project's bin\debug directory. Then:

1. Use ILDASM to disassemble the Outlook wrapper DLL and get IL source. You
can find ildasm under "\Program Files\Microsoft Visual Studio
.NET\FrameworkSDK\Bin. The command line should look like:

ildasm "<project path>\bin\debug\interop.outlook.dll" /OUT outlook.il

2. Open Outlook.il in a text editor -- you can open under VS.NET using the
File\Open\File... command from the menu.

Look for classes with a suffix of "_SinkHelper". For example: ".class
private auto ansi sealed ItemEvents_SinkHelper". Change each instance of
such classes and replace 'private' with 'public'.
The example above should now appear as ".class public auto ansi sealed
ItemEvents_SinkHelper". Repeat this for all the other '_SinkHelper' classes.
Save the modified file.

3. Rebuild the Interop.Outlook.DLL using ILASM with the /DLL option. ILASM
can be found in the
"\WINNT\Microsoft.NET\Framework\v<n.n.n>\ directory. The command line should
look like:

ilasm <path>\outlook.il /DLL

This will generate a file called Outlook.dll. Rename this to
Interop.Outlook.DLL and replace the original version of this file in the
bin\debug directory.

4. Right click on the Outlook reference in the Solution Explorer and change
Copy Local to False. This prevents the original Outlook wrapper from being
regenerated.

Hope this helps. If you still have problems maybe you can post the source
code for your Add-in.

- Mike


> > 3. In the Solution Explorer, add a reference to Microsoft Outlook
Library
> > 9.0.
> > Set the Copy Local property for this reference to False to inhibit
> > regeneration of the Outlook COM wrapper.
>

> I have only verion 9.1 (listed as 10.0) of the object model DLL, so I
tried
> it with that.


>
>
> > 4. Rebuild the Outlook wrapper (Interop.Outlook.DLL) making the Sink
> Helper
> > methods public, and deposit the resulting DLL into the debug directory.
>

> How do you do this part? I don't understand how I am supposed to rebuild
> the DLL and make the Sink Helper methods public...


>
>
> > 5. Under the Configuration properties\Debugging in the property pages
for
> > the Addin, set the Start Application to the path for the Outlook
> executable.
>

> Done.


>
>
> > 6. Build the Add-in and then start a debug session.
>

> Done.
>
> With the directions above, I could not make it compile. It could not find
> the types or namespaces 'EnvDTE', 'Office' and "IDTCommandTarget'. I also
> had to add references to System.Windows.Forms. I think the previous
errors
> were because my Sink Helper methods were not public in the
> Interop.Outlook.dll DLL? I don't know how to rebuild it.
>
> Thanks again,
> Neema
>
>


0 new messages