Monday, February 14, 2011

Running .NET applications in-process using AppDomains

When testing a compiler for a managed language a very convenient end-to-end testing technique is to compile a test program, then run it and verify that it gives the expected output. Not only you cover all parts of the compiler in this manner (parser, binder and emitter), but also you verify that your compiler produces correct IL (otherwise the CLR won’t load and verify your assembly) and your final program has the expected behavior (output has to match).

One downside is that if you have 50,000 test programs, you have to pay the process startup cost and the CLR startup cost 50,000 times. AppDomains to the rescue – they were originally designed as lightweight managed processes, so why not use them as such?

To demonstrate this approach, we’re going to write a .NET program that can run any .NET Console application, intersept its output to the Console, and print it out for demo purposes. First of all, let’s create a C# console application that prints out “Hello World” and save it as Program.exe. Then let’s create a sample “verifier” program that will start and run Program.exe without spinning up a separate process and a separate CLR instance:

using System;
using System.IO;
using System.Reflection;
namespace AppDomainTools
{
   public class Launcher : MarshalByRefObject
   {
       public static void Main(string[] args)
       {
           TextWriter originalConsoleOutput = Console.Out;
           StringWriter writer = new StringWriter();
           Console.SetOut(writer);
           AppDomain appDomain = AppDomain.CreateDomain("Loading Domain");
           Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(
               typeof(Launcher).Assembly.FullName,
               typeof(Launcher).FullName);
           program.Execute();
           AppDomain.Unload(appDomain);
           Console.SetOut(originalConsoleOutput);
           string result = writer.ToString();
           Console.WriteLine(result);
       }

       /// <summary>
       /// This gets executed in the temporary appdomain.
       /// No error handling to simplify demo.
       /// </summary>
       public void Execute()
       {
           // load the bytes and run Main() using reflection
           // working with bytes is useful if the assembly doesn't come from disk
           byte[] bytes = File.ReadAllBytes("Program.exe");
           Assembly assembly = Assembly.Load(bytes);
           MethodInfo main = assembly.EntryPoint;

Read more: Kirill Osenkov