Type Resolving


Thursday, January 31st, 2008 at 9:41 am, .net

When you write an application for the .net framework it’s very common to separate the various parts into visual studio projects which will be represented in separate assemblies (dll or exes) after compiling them.

This is usually done like that so other applications can link those assemblies without needing to include a whole stack of drivers for a little piece of logic (you know, reusability, reliability, blablabla…) So you end up with two or more applications/dlls depending the same dll.

Nothing special until here.

Deployment

So what happens when the user, like in my case, puts the executable out of directory with the shared dlls?

Exactly, the program cannot be started and fails with a TypeLoadException because it could not load all its dependencies.

The .net way to solve this is by putting the shared dlls into the global assembly cache (GAC). However, this requires the dlls to be signed with a key and will cause a lot of work if you have sub-dependencies because every signed component needs all its references to be signed as well.

And if you sign all the dlls you change the signature of them and break any existing client/dll. That will cause a big redeployment just because somebody wasn’t happy with a simple link and wanted to put the whole exe to his desktop (and we have lots of dlls, we explicitly decided not to spam the GAC and had some other reasons as well)

But of course there will be a solution for this I thought.

Assembly specific configuration is normally defined by a .config file which has the same name like the assembly itself. E.g.

MyApp.exe
MyApp.exe.config

Loads the xml configuration and applies it to the dll/exe while it gets loaded. With “probing” you can specify further directories where the class loader should look for the required types if they could not be found in the expected location. Anyway, you cannot know where the shared directory is located when you write the config file but I let that for now.

<configuration>
<runtime>
<assemblybinding xmlns="urn:schemas-microsoft-com:asm.v1">
	<probing privatepath="dirA;dirB"></probing>
</assemblybinding>
</runtime>
</configuration>

So I referred to my shared dll directory but guess what, it didn’t work! msdn tells me that those probing paths must not be above the application’s root path. Ha! how nice..

Fortunately, I have a remoting server running to which my client apps need to connect anyway so I could implement a way cooler solution.

TypeLoadException

When the client application could not resolve the dependency it calls some handlers which let the developer handle such cases before quitting.
You can register yourself for such cases with

AppDomain.CurrentDomain.AssemblyResolve +=
	new ResolveEventHandler( DoResolve );

and handle it in a method with this signature

System.Reflection.Assembly DoResolve(
	object sender, ResolveEventArgs args)

My plan

When my event handler method gets called, I call my remoting server and ask
“Hey mate, I need an assembly called ‘args.Name’. You have such a thing?”
“Sure. It’s located in MyCompany\Shared\MyCompany.foo.bar.dll”

When I have the the path information I can do something like

System.Reflection.Assembly.LoadFile( path );

and my client will load that assembly and finds the needed type information in it.

Thus in the end it looks like

private Assembly DoResolve(
	object sender, ResolveEventArgs args )
{
  String path = myServer.GetAssemblyLocation( args.Name );
 
  if( path == null )
	return null;
  else
	return System.Reflection.Assembly.LoadFile( path );
}

In the end the only dll left in the GAC is a dll where the resolving is implemented. Thus the only thing the client must do is calling helperDllInGac.AddAutoResolving() in the first line of its main() and a handler for type loading errors in the current AppDomain will be registered and all dlls will be resolved dynamically.

With this mechanism you can move all executables to any place and they will find their dependencies as long as the service is running. This may not be necessary in your case, but if you have lots of little tools it gets very annoying when you need to sign and register almost every dll because you cannot be sure that executable will be in the same directory.

Instead of returning a path you could also provide a byte array representing the assembly in the servers resolving method and gain even more flexibility. However, depending on what you are doing this it not a real solution and may make things even more complicated and slow.

Notes

Stack

AddAutoResolving() must be called *before* any external types appear while the stack gets built.
E.g.

void main( String[] args )
{
	new Helper().AddAutoResolving();
	MyExternalType t;
	new MyGUIApp();
}

will fail because while the CLR builds the method stack it has no idea about the unknown type yet, calls the handlers but there aren’t any registered yet and the app fails anyway.

LoadFile

LoadFile is just for people who know what they are doing. If you need those anti-dll-hell-features like versioning and policy don’t use it.

Tags: , ,

7 Responses so far

  1. luca Says:

    Öhhh.. now thats really cool. And you can overload it by letting a normal assembly in the executable-directory, isnt it?

  2. steve Says:

    yes :)

  3. Chrigi Says:

    If you need those anti-dll-hell-features like versioning and policy don’t use it [LoadFile]

    Im not all into that dotnet thing, but why you say so? I thought that gac stuff would support the application in such cases.

  4. steve Says:

    Hey jo my co-traveler :p

    The GAC can store different versions of a dll and a .NET application will get its non-local references from the GAC.

    Depending on the configuration of the reference it will load any version of a dll or a specific one from the GAC and fails if the attempt was not successful.

    Now it is possible to override this Load-From-GAC-behaviour by providing a global or local xml file which “rebinds” the dll (you use such a thing when you deployed a new, 100% backwards compatible (Muahahaha) dll and want that your clients are using it without noticing it.)

    So if you use LoadFile(…) you bypass all those mechanism because you directly reference to a possible incomatible dll. Also when rebindings exist, they will be ignored.

  5. SanAntonio Says:

    Famous remarks are very seldom quoted correctly.

  6. zaklady bukmacherskie Says:

    Ciekawa strona, bede ja odwiedzal czesciej, pozdro

  7. zak Says:

    Ciekawy post, dodalem twoj blog do ulubionych, bede tu teraz wpadal czesciej, pozdrawiam

Leave a Reply