views:

1202

answers:

1

There is very strange assembly reference problem and loading problem I experience with my Outlook addin. Here are the detail (a long story :) ):

I have an old Outlook addin, written and build using .Net 1.1. The addin is loaded using an unmanaged shim in its own application domain. It works OK, with .Net 2.0, even if 1.1 is not present on the user's machine.

The addin uses a custom Outlook interop assembly, created by VS 2003 against Outlook 2000, and after that rebuild to be strongly-named (as is my addin).

In the addin project I reference only this custom interop assembly, no reference to the official MS interop assemblies.

When this addin is used in an environment with Outlook 2007 and .Net 2.0, where the official MS interop assemblies are installed in GAC, for some reason I see that the addin loads and uses them.

In the code of the Connect class, I have a using directive:

using Outlook;

which is the namespace of my custom interop assembly.

In Connect ctor I have these lines of code (added for testing purposes):

Assembly.LoadFrom(PATHTOMYASSEMBLY + "Interop.Outlook.dll");
Type type = typeof(Outlook.ApplicationClass);
logger.Debug("Outlook.Application full type is: {0}", type.AssemblyQualifiedName);

This outputs:

Outlook.Application full type is: Outlook.ApplicationClass, Interop.Outlook, Version=9.0.0.0, Culture=neutral, PublicKeyToken=4cfbdc5349cf59d8

Which is exactly what I would expected.

The problem is, that when the OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) is called, I see in the log (I have a hook to AssemblyLoad event of the current domain) that MS interop assembly is loaded as well:

private void app_domain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
    Assembly loadedAssembly = args.LoadedAssembly;
    logger.Debug("Assembly {0} is loaded from: {1}", loadedAssembly.FullName, loadedAssembly.GlobalAssemblyCache ? "GAC" : loadedAssembly.Location);
}

Output:

Assembly Microsoft.Office.Interop.Outlook, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c is loaded from: GAC

My OnConnection method starts like this:

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
    Type type = application.GetType();
    logger.Debug("OnConnection application object's full type is: {0}", type.AssemblyQualifiedName);

    Outlook.Application applicationObject = (Outlook.Application)application;

This outputs:

OnConnection application object's full type is: Microsoft.Office.Interop.Outlook.ApplicationClass, Microsoft.Office.Interop.Outlook, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c

This is really weird, as you can see that on the next line I can successfully cast to Outlook.Application without any problems.

I have checked with Reflector, and my assembly DOES NOT reference Microsoft's interop assemblies in any way. Same for my Interop.Outlook.dll.

So, does someone knows what's going on? What is the answer for these questions:

  1. Why it loads the Microsoft assemblies at all?

  2. How it is possible to cast between unrelated classes/interfaces, defined in different assemblies?

NOTE: I have created a new addin, very simple one, which does nothing, just loads. I could reproduce the problem, so really, does anyone knows how CLR decides what interop to load and from where. Besides in GAC, is there another place (registry???) where there is a link between a COM object and the interop it requires?

+1  A: 

I think I found the answer to your problem in Microsoft's Primary Interop Assemblies primer. PIAs are handled differently in Visual Studio:

When a user attempts to add a reference to a type library that has a registered PIA, Visual Studio will silently use the registered PIA instead of reimporting the type library with Tlbimp. This ensures that the PIA is used whenever possible.

This means that when you add a reference to your own IA to the project, Visual Studio will check whether there is a PIA registered for this COM object. The Outlook PIAs are registered under the following key:

HKEY_CLASSES_ROOT\CLSID\{0006F023-0000-0000-C000-000000000046}\InprocServer32\12.0.0.0
    Assembly="Microsoft.Office.Interop.Outlook, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C"

From what I understand unregistering the PIA using the regasm tool should remove the key and re-adding the reference to your own IA should give the expected result.

However, Microsoft does not recommend to use custom IAs if there is a PIA available. I don't understand the exact reason for this, but I assume this might be related to marshaling optimizations and having unique type definitions.

0xA3
thanks divo. one problem with the answer - as I have put in the question, my interop assemblies are strong-named, so it still does not solve my problem.
Sunny
Sorry, i missed the strong-naming thing, but i revised my answer.
0xA3
Ok, but how comes, that I do not see references to these PIAs in my compiled code? As I said, I checked with reflector, and there are no references at all.
Sunny
The reason could be the way Reflector extracts references from an assembly. Afaik references are not stored explicitly in an assembly only the type is stored and Reflector lists all the types used.
0xA3
Thanks. Any idea how I can override this? Possibly by changing something in the project file?
Sunny
What is actually the problem that the PIA is loaded instead of your IA? Are there any exceptions thrown?
0xA3
I experience some problems with some objects not properly released, and I want to be sure that it is not caused by this behavior. It's really hard to track down what's going on, so I want to eliminate some possibilities.
Sunny
If it is for troubleshooting only you could simply uninstall the PIAs from the GAC using gacutil.exe
0xA3
agree. the second point is self-education. looks like its a dark magic how all this work.
Sunny