Assembly lookup , GAC and plugins

A

Arnaud Debaene

Hello group.

I have an app which can load "plugins" assemblies that are described
in the registry (each registry entry gives the full path to the plugin
..dll file). The plugins may be anywhere on the disk.

Each plugin defines a class which implements an interface known to the
app. The main app loads the plugin with Assembly.LoadFrom and then
uses Assembly.GetTypes() and Type.FindInterface to find the concrete
types in the plugin that implements the interface. An object of this
concrete type is then constructed by reflection (ConstructorInfo ->
Invoke).

All of this is rather easy stuff. Now a plugin and the main app
depends both on an utility assembly (let's call it Utils.dll).
Utils.dll and MainApp.exe are strong-named, Plugin.dll is not.

My problem is that the main app and the plugin are compiled at
different times and therefore reference different versions of
Utils.dll :
MainApp.exe --> Utils.dll V 1.0.0.0
Plugin.dll --> Utils.dll V 1.0.1.0

Plugin.dll is in a directory unrelated to MainApp. Therefore, I must
put Utils.dll V 1.0.1.0 in the GAC because Plugin.dll's path is not
looked for when loading the plugin.

Now, I can put Utils.dll v 1.0.0.0 either in MainApp directory, either
in GAC. But in both cases, I got a FileNotFoundException : "File or
assembly name Utils, or one of its dependencies, was not found" when
calling Assembly.LoadFrom("MyPlugin\Plugin.dll").
It seems that, although Utils.dll is strong-named, since Utils.dll V
1.0.0.0 is already loaded in the AppDomain, the GAC is not looked for
the correct version of Utils (1.0.1.0) and therefore loading fails.

Has anyone seen such a behaviour, and what would be the workaround?
I've got no trace in fuslogvw (why??).


Thanks,
Arnaud
MVP - VC

PS : Utils.dll has no dependencies itself (except on the framework).
There is no publishher file and no application configuration file
(since plugins may be distributed after the app)
 
D

David Levine

My problem is that the main app and the plugin are compiled at
different times and therefore reference different versions of
Utils.dll :
MainApp.exe --> Utils.dll V 1.0.0.0
Plugin.dll --> Utils.dll V 1.0.1.0

Plugin.dll is in a directory unrelated to MainApp. Therefore, I must
put Utils.dll V 1.0.1.0 in the GAC because Plugin.dll's path is not
looked for when loading the plugin.

Now, I can put Utils.dll v 1.0.0.0 either in MainApp directory, either
in GAC. But in both cases, I got a FileNotFoundException : "File or
assembly name Utils, or one of its dependencies, was not found" when
calling Assembly.LoadFrom("MyPlugin\Plugin.dll").
It seems that, although Utils.dll is strong-named, since Utils.dll V
1.0.0.0 is already loaded in the AppDomain, the GAC is not looked for
the correct version of Utils (1.0.1.0) and therefore loading fails.

If the plugin is compiled against utils.dll, v1.0.0.0 and utils is signed
with a strong name, then the runtime will load it if it can find it. As long
as the assembly is signed the runtime can load two different versions of the
same dll. The runtime can even load the same version of the dll twice, one
in the Load context, and another copy in the LoadFrom context (although this
is usually not what you want to happen).

Have you tried signing the plugin.dll to see if this changes the behavior? I
would not expect it too, but there may be other things going on.
Has anyone seen such a behaviour, and what would be the workaround?
I've got no trace in fuslogvw (why??).
You can install a publisher policy for Utils.dll that specifies that the
latest version of the dll should always be the one used - I don't know if
this is what you want but it would avoid the problem you're seeing.

Regardless, you could subscribe to the AssemblyResolve event and use that to
manually locate the Utils.dll for the plugin.
 
A

Arnaud Debaene

David Levine said:
If the plugin is compiled against utils.dll, v1.0.0.0 and utils is signed
with a strong name, then the runtime will load it if it can find it. As long
as the assembly is signed the runtime can load two different versions of the
same dll.
That's what I thaugt and that's why I don't understand the behaviour I
get. I'm gonna do additionnal tests.
The runtime can even load the same version of the dll twice, one
in the Load context, and another copy in the LoadFrom context (although this
is usually not what you want to happen).
What is exactly a Load Context? Ther is very few information on the
subject. All I got in MSDN is KB 327435 (but this doesn't apply to
..NET 1.1 and VS2003, does it?) and the cryptic sentence in
Assembly.Load documentation :
"The Load methods use the default load context, which records the
assembly name and assembly instance information for the set of
assemblies that is the transitive closure of the assemblies referenced
by a managed application" Huhhh????? How does it help with finding a
new assembly?
Have you tried signing the plugin.dll to see if this changes the behavior? I
would not expect it too, but there may be other things going on.
I will try asap.
You can install a publisher policy for Utils.dll that specifies that the
latest version of the dll should always be the one used - I don't know if
this is what you want but it would avoid the problem you're seeing.


Regardless, you could subscribe to the AssemblyResolve event and use that to
manually locate the Utils.dll for the plugin.

Now, this is interesting! I wasn't aware of this event. I will use it
in my plugin loader mechanism. I could consider that all dependencies
of a plugin are located in the plugin directory itself (which will
also prevent me from clobbering the GAC with various versions of
utils.dll).

Thanks for the hints.

Arnaud
MVP - VC
 
D

David Levine

The runtime can even load the same version of the dll twice, one
What is exactly a Load Context? Ther is very few information on the
subject. All I got in MSDN is KB 327435 (but this doesn't apply to
.NET 1.1 and VS2003, does it?) and the cryptic sentence in
Assembly.Load documentation :

The KB article does not really address the Load versus LoadFrom context.

What it boils down to is that the runtime's fusion layer keeps multiple
internal lists of assemblies. Assemblies in the Load context can
automatically bind to other assemblies in the Load context, and references
to types defined in those assemblies can be resolved by the runtime without
external assistance. Assemblies in the LoadFrom context cannot automatically
bind to other assemblies in the LoadFrom context (at least, not as far as I
know), and references to types must be manually resolved.

What this means is that if Assembly a is in the LoadFrom context and it has
a static reference to asm B (e.g. in the same directory as asm A but not
loaded into fusion), then unless asm B is in a directory where fusion can
locate it automatically (e.g. the GAC, or the appbase) it will not load, and
will need to manually loaded by your own code (hence you need to hook the
AssemblyResolve event).

Further, if your code accesses a type defined in an assembly in the LoadFrom
context, then when the runtime resolves that type it will have to access the
assembly, but because it is not in the Load context it will generate another
AssemblyResolve event even though the correct assembly is already loaded in
the LoadFrom context.
"The Load methods use the default load context, which records the
assembly name and assembly instance information for the set of
assemblies that is the transitive closure of the assemblies referenced
by a managed application" Huhhh????? How does it help with finding a
new assembly?
That fancy terminology means something along the lines of loaded all
required assemblies, including others referenced by other referenced
assemblies. In other words, if loading asm A requires loading asm B and asm
B requires asm C, then you wind up needing all 3 assemblies. If one is
loaded in the Load context and it can resolve the transitive closure, then
all 3 end up in the Load context.

Now, this is interesting! I wasn't aware of this event. I will use it
in my plugin loader mechanism. I could consider that all dependencies
of a plugin are located in the plugin directory itself (which will
also prevent me from clobbering the GAC with various versions of
utils.dll).

If you are using LoadFrom then you absolutely need to become intimately
familiar with that event. Simply putting the other assemblies in the same
directory as where you loaded the other assembly does not guarantee that the
runtime will look there for automatically loading other assemblies.
 

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