Hatted Rooster
Hatted Rooster

Reputation: 36503

Load manifest assemblies dynamically into appdomain

I have 2 projects:

Main (Console)
Util (Class library)

I want the best of both worlds and I'm unsure if this is possible.

Say I have this class in my Util library:

public static class Helper
{
    public static int AnswerToUniverse()
    {
        return 42;
    }
}

I add the class library Util as a dependency to Main through Visual Studio Project -> Add Reference. This puts it in the manifest of Main and allows me to use all the types exposed in the Util library statically, with all the Intellisense goodness.

I always thought that the runtime loads these statically linked assemblies right on startup but it seems if I remove all uses from Util in my Main project but keep it as a reference and then remove the .dll assembly file from the working directory of Main that it still runs fine so I concluded that it gets loaded upon first use.

Now, I won't have the assembly file Util in my working directory but I do have the raw bytes of it so I'd like to dynamically load the content of the Util assembly as raw bytes into the app domain at runtime before the first use of any of its methods.

I could remove the static dependency and then use reflection, something like:

var assembly = AppDomain.CurrentDomain.Load(rawBytes);
assembly.GetType("Util.Helper").GetMethod("AnswerToUniverse") // etc.. etc..

But I'd very much still like to be able to just do:

var answer = Helper.AnswerToUniverse();

I've already tried to hook AppDomain.CurrentDomain.AssemblyResolve but that doesn't seem to get called on statically referenced assemblies.

Test code:

Util, Helper.cs:

namespace Util
{
    public static class Helper
    {

        public static int AnswerToUniverse()
        {
            return 42;
        }
    }
}

Main, Program.cs:

using Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Main
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
            Console.WriteLine(Helper.AnswerToUniverse());
        }

        private static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            File.WriteAllText("test.txt", "assemblyresolved");
            Console.WriteLine("resolved.");
            Console.Out.Flush();
            return null;
        }
    }
}

Every time I run my Main without the Util assembly file in the working directory I get a FileNotFoundException, is this somehow interceptable so I can return the assembly loaded from the raw bytes or am I stuck using reflection as shown above?

Upvotes: 0

Views: 218

Answers (1)

David Browne - Microsoft
David Browne - Microsoft

Reputation: 89396

I'd like to dynamically load the content of the Util assembly as raw bytes into the app domain

I've already tried to hook AppDomain.CurrentDomain.AssemblyResolve

That's the correct approach. The AssemblyResolve will be invoked when the JITter needs to compile any type in the target assembly.

And since JIT triggers this, you have to hook AssemblyResolve before the JITter tries to compile any references to the target assembly. So this

static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    Console.WriteLine(Helper.AnswerToUniverse());
}

Won't work, as the compilation of Main will trigger the loading of the Helper assembly, but you haven't hooked AssemblyResolve yet.

And because

The loader creates and attaches a stub to each method in a type when the type is loaded and initialized. When a method is called for the first time, the stub passes control to the JIT compiler, which converts the MSIL for that method into native code and modifies the stub to point directly to the generated native code. Therefore, subsequent calls to the JIT-compiled method go directly to the native code.

Compilation by the JIT Compiler

It's enough to push the first use of Helper into a seperate method (and prevent that method from being "inlined" into Main), so Main can be compiled start executing before the first time Helper is needed. Eg

static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    Run();
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void Run()
{
   Console.WriteLine(Helper.AnswerToUniverse());
}

Upvotes: 1

Related Questions