6

I'm making a program depending on some DLLs included in a third party program. I'm not allowed to distribute these DLLs myself. The third party program must be installed for my program to work.

How can i make a reference to these DLLs? I know the exact location of them through a registry key set by the program.

I have tried to add the files in Project->References and set CopyLocal to false but when i start i then get a FileNotFoundException "Could not load file or assembly".

I have tried to add an event to AppDomain.CurrentDomain.AssemblyResolve and load the files there but the problem is that i get the exception before my program even starts. Even if i put a breakpoint on the first line the exception will be thrown before the breakpoint is hit.

aero
  • 262
  • 2
  • 10
  • In general 3rd party vendors explain how to use their component in production if they don't follow standard practise (which seems to be the case here). Don't you have a decent documentation? – Simon Mourier Aug 07 '11 at 10:05
  • As simon says there is usually a distributable agreement with 3rd party assemblies - alternatively if they are strong named you put them in the GAC? – Shaun Wilde Aug 07 '11 at 10:08
  • Are you subscribing to the `AssemblyResolve` event at the earliest possible opportunity? If you do not subscribe early enough, then some code may run that depends on your 3rd party assembly before you can resolve it. – Tim Lloyd Aug 07 '11 at 10:16

2 Answers2

10

From C# 3.0 in a Nutshell, 3rd edition, by Joseph and Ben Albahari, p. 557-558:

Deploying Assemblies Outside the Base Folder

Sometimes you might choose to deploy assemblies to locations other than the application base directory [...] To make this work, you must assist the CLR in finding the assemblies outside the base folder. The easiest solution is to handle the AssemblyResolve event.

(We can ignore the fact that in your case, someone other than you is deploying the assemblies.)

Which you tried. But a very important clue follows somewhat later. Read the two code comments:

public static void Loader
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += FindAssem;

        // We must switch to another class before attempting to use
        // any of the types in C:\ExtraAssemblies:
        Program.Go();
    }

    static Assembly FindAssem(object sender, ResolveEventArgs args)
    {
        string simpleName = new AssemblyName(args.Name).Name;
        string path = @"C:\ExtraAssemblies\" + simpleName + ".dll";

        if (!File.Exists(path)) return null;
        return Assembly.LoadFrom(path);
    }
}

public class Program
{
    public static void Go()
    {
        // Now we can reference types defined in C:\ExtraAssemblies
    }
}

As you see, the class where you resolve the external assemblies must not refer to any type from any of the external DLLs anywhere. If it did, code execution would stop way before your AssemblyResolve ever gets a chance to run.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • Bingo! Moving the usage of the referenced items into another class than the one with Main() solved it. Thanks :) – aero Aug 07 '11 at 10:40
  • P.S.: I should admit that I do not know whether this solution is officially documented, or whether it's just a clever trick that happens to work with some framework versions, but might fail with others. Therefore I fully agree with Hans Passant's final statement: There should be an easier way. – stakx - no longer contributing Aug 07 '11 at 10:44
  • This is great, thx! Questions: if FindAssem() returns null, how do I prevent so that Go() won't throw FileNotFoundException? – Eddie Oct 09 '13 at 19:12
  • @Eddie: If I remember correctly, there's nothing you can do about that except for making `FindAssem` smarter so it doesn't return `null` in the first place: The code that is about to be run requires some type from an assembly, which `FindAssem` cannot find... once you subscribe a method (`FindAssem`) to the `AssemblyResolve` event, that method becomes the Framework's last resort for locating assemblies. If that method doesn't find the assembly, the runtime environment decides that the needed type is not available, and prevents further execution by throwing an exception. – stakx - no longer contributing Oct 09 '13 at 21:51
4

Your app bombs because the JIT compiler is the first one that needs to load the assembly. You'll need to carefully avoid using types from the assembly in your Main() method. That's not hard to do, just write another Main method and give it an attribute that tells the jitter that it should never inline that method. Without the attribute it may still bomb in the Release build when the optimizer inlines the method. Like this:

using System;
using System.Runtime.CompilerServices;

class Program {
    static void Main(string[] args) {
        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
        DelayedMain(args);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void DelayedMain(string[] args) {
        // etc..
    }


    static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
        // etc...
    }
}

Before you commit doing it this way, do contact the vendor and ask for recommendations. There has to be an easier way to exercise your license rights. The GAC would be a common choice, maybe you just need to run gacutil on your dev machine.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536