You need a load of extra functionality to support that

One of the interesting things the new K runtime offers the programmer, is the rather interesting notion of an Assembly Neutral interface, which allows separately compiled components to target the same (from the CLR perspective) type, without that type having been provided to them at compile time. David Fowler talks a little about the implementation here, and after reading it I could understand how things looked from the assembly point of view, but I didn’t see any explanation of how the assembly loading happens. Fortunately all of the code is available on GitHub so it is possible to do a little bit of investigation.

The idea of these neutral types is that they are pulled into separate assemblies (named in a way that matches across separate compilations) and these separate assemblies are actually saved as a resource in the assembly that includes them. The AssemblyNeutral definitions themselves are also removed from the original assembly, by removing them from the compilation unit that produces it.

We can simulate the look of the eventual assembly by generating an assembly, PretendNeutralAssembly from this code:

namespace PretendNeutralAssembly
{
    public class PretendNeutralInterface
    {
        public void TestMethod()
        {
            Console.WriteLine(“Called”);
        }
    }
}

And then simulate the Roslyn produced main assembly by compiling the following code. Remember that we are pretending that the above code and the code that follows start in the same compilation unit, and the above code is initially marked with the AssemblyNeutral attribute. Roslyn would be used to extract the above code into a separate assembly, which would be referenced by that shown below.

namespace PretendNeutralAssemblyConsumer
{
    public class PretendNeutralConsumer
    {
        public void Worker()
        {
            var instance = new PretendNeutralAssembly.PretendNeutralInterface();
            instance.TestMethod();
        }
    }
}

When we compile the above, we both reference the PretendNeutralAssembly and also include it as a resource in the final output.

embedd

The idea is that multiple assemblies that contain a particular AssemblyNeutral type will all end up with a resource containing the compiled definition of that type. The trick to consuming these interfaces is that we will be loading them inside a host, and this host will be able to hook the assembly loading mechanics of the CLR in order to extract the embedded types. If multiple assemblies define the same neutral type, then they will share the first embedded assembly that is loaded, keeping the CLR happy with its stricter notion of type equality.

The host will override the Resolve method on the AppDomain, giving it a chance to help the CLR find missing assemblies. In particular, the assemblies that are held in the resources which the CLR’s loader knows nothing about. We could wait until the assembly is needed and then go back and search for embedded assemblies, or we can pre-emptively scan for embedded assemblies whenever we load an assembly. The implementation does the latter, adding them to Dictionary so that they can be found later.

We can get our demonstration assembly working by hosting it in an assembly that has the following code.

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

    var loadedAssembly = Assembly.LoadFrom(“PretendNeutralAssemblyConsumer.dll”);

    // We need to exercise the loaded type, to get the JIT to load the referenced types.
    // We’ll just call the Worker method on a created instance.

    var loadedType = loadedAssembly.DefinedTypes.First();
    var instance = Activator.CreateInstance(loadedType);
    var method = instance.GetType().GetMethods().First();
    method.Invoke(instance, null);
}

private static Dictionary<string, Assembly> s_cache = new Dictionary<string, Assembly>();

// When an assembly has been loaded deal with any embedded resource assemblies
private static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
    var assembly = args.LoadedAssembly;
    // We just look for the specific assembly – in the real code we’d need to look at
    // all of the names to find the assemblies.

    var stream = assembly.GetManifestResourceStream(“PretendNeutralAssemblyConsumer.PretendNeutralAssembly.dll”);
    if (stream != null)
    {
        var assemblyAsBytes = new byte[stream.Length];
        stream.Read(assemblyAsBytes, 0, (int)stream.Length);
        var loadedAssembly = Assembly.Load(assemblyAsBytes);
        s_cache.Add(loadedAssembly.FullName, loadedAssembly);
    }
}

// If the CLR can’t find the assembly, see if it a neutral type assembly that it is looking for
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    var target = args.Name;
    return s_cache[target];
}

AssemblyNeutral types seem like a useful idea. In the real implementation, they will probably only allow interfaces to be marked as AssemblyNeutral, and we are going to be in the usual world of keeping an interface type unchanged once it is published to the world –otherwise there will be a range of interesting loader exceptions if the definition for a type which the CLR loads is different from the neutral definition that a subsequently loaded assembly was compiled against. In some ways it is shame that this requires a special host, but the current implementation at least gives us a way to experiment with the idea without changing the CLR in any way.

You can see the real KRuntime implementation of this in the Github sources on line 121.

Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s