Combine multiple .NET assemblies

 
 
  • Gérald Barré

Applications often consist of an executable and several DLLs. To deploy this type of application you have to copy all the files. It would be more convenient to deploy only one file: the executable. We could use ILMerge or ILRepack which work pretty well, but we can also do it just from Visual Studio.

The idea is to copy the dependencies into the application's resources. For this, you have to copy the DLLs in the project (it is possible to add them as a link) and define the "Embedded Resource" compilation action.

At runtime, you must indicate that the assembly is in the resources of the executable. For that, we will use the event AppDomain.AssemblyResolve. This event allows us to return the requested assembly when it is not found automatically. It is also interesting to load the associated symbol file (PDB file).

C#
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string baseResourceName = Assembly.GetExecutingAssembly().GetName().Name + "." + new AssemblyName(args.Name).Name;
    byte[] assemblyData = null;
    byte[] symbolsData = null;
    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(baseResourceName + ".dll"))
    {
        if (stream == null)
            return null;

        assemblyData = new byte[stream.Length];
        stream.Read(assemblyData, 0, assemblyData.Length);
    }

    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(baseResourceName + ".pdb"))
    {
        if (stream != null)
        {
            symbolsData = new Byte[stream.Length];
            stream.Read(symbolsData, 0, symbolsData.Length);
        }
    }

    return Assembly.Load(assemblyData, symbolsData);
}

The function is not interesting, we get the name of the requested DLL, and we load it from the resources of the executable.

In use there is a subtlety: do not use a DLL directly from the Main method. Indeed, if this is the case, the assembly must be resolved before we could subscribe to the AssemblyResolve event ⇒ crash. The Main method must therefore contain the subscription to the event and the call to the main method (which can be decorated with the [MethodImpl(MethodImplOptions.NoInlining)] attribute).

C#
static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    if (Debugger.IsAttached)
    {
        SafeMain();
    }
    else
    {
        try
        {
            SafeMain();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void SafeMain()
{
    Console.WriteLine(Lib1.Test.Add(1, 2));
}

There you go!

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub