Bootstrapping your Application's AppDomainManager

Last time I mentioned that when using pure managed code to setup an AppDomainManager, you should prefer to use the environment variables rather than the registry keys.  Once you've decided to use the environment variables, you need to determine a strategy for setting them up.  Ideally, you'd want the environment setings to affect only your process and no other managed applications.  In order to pull that off, it's best to set the environment variables in only your managed process.

We can easily do that by using the EnvironmentVariables collection exposed on the ProcessStartInfo class when starting off a new process.  However, using Process.Start means that we have a shim application which sets up the environment and then kicks off our real application.  Shipping two .exe's can be somewhat ugly -- and you have a problem if someone invokes your application .exe without going through the shim, so we'd like a better solution.

What if we combine the functionality of the shim application into our main application's .exe?  In the entry point we could do a check to make sure that the AppDomainManager is setup -- if it is, continue on, if not then set it up and respawn.  This actually turns out to be pretty easy to do in a few lines of code.  Here's a sample application ADMApp, which expects to be run with CustomAppDomainManager.  Combining the shim with the application itself would produce code that looks something like this:

public static class ADMApp
{
    public static int Main(string[] args)
    {
        // determine if we need to setup an AppDomainManager, or if we've already done that
        // and can begin executing our application's logic
        AppDomainManager domainManager = AppDomain.CurrentDomain.DomainManager;
        if(domainManager != null && domainManager.GetType() == typeof(CustomAppDomainManager))
        {
            // remove the .exe name from args[0]
            string[] realArguments = new string[args.Length - 1];
            for(int i = 0; i < realArguments.Length; i++)
                realArguments[i] = args[i + 1];

            return StartApplication(realArguments);
        }
        else
        {
            return SetupAppDomainManager();
        }
    }

    /// <summary>
    /// Start a new version of this process, using an AppDomainManager this time
    /// </summary>
    private static int SetupAppDomainManager()
    {
        // create a new copy of this application
        ProcessStartInfo psi = new ProcessStartInfo(
            typeof(ADMApp).Assembly.Location,
            Environment.CommandLine);

        // setup the AppDomainManager environment variables
        psi.UseShellExecute = false;
        psi.EnvironmentVariables.Add(
            "APPDOMAIN_MANAGER_ASM",
            typeof(CustomAppDomainManager).Assembly.FullName);
        psi.EnvironmentVariables.Add(
            "APPDOMAIN_MANAGER_TYPE",
            typeof(CustomAppDomainManager).FullName);

        // start the new copy
        Process process = Process.Start(psi);
        process.WaitForExit();
        return process.ExitCode;
    }

    /// <summary>
    /// Real entry point
    /// </summary>
    private static int StartApplication(string[] args)
    {
        // ...
    }
}

Lets take a look at the code in some more detail.

In Main, the first thing we do is check to see if there's already an AppDomainManager setup for this process.  If there is, we do a second check -- to make sure that it's the same AppDomainManager we're expecting.  This prevents us from accidentally using somebody else's AppDomainManager if they had already setup the environment variables or the registry keys.

If we find that either condition is not true, we call SetupAppDomainManager, which is where the real work gets done.  It starts by creating a new ProcessStartInfo that will start the .exe which we're currently running in, passing along our command line parameters.  One issue we'll have to deal with later is that Environment.CommandLine includes the application name as the first argument, so we'll have to filter that out.

Once we've got our ProcessStartInfo, we set UseShellExecute to false, since that's a requirement to use the EnvironmentVariables collection.  We then set the AppDomainManager variables up.  Setting these variables is another reason that this method of bootstrapping your application works nicely -- you don't have to hard code type and assembly names, the CLR will resolve those for you.

Now that we've setup the context for our new process to run under, we kick it off, wait for it to exit, and return its exit code.  That exit code gets propagated out of our Main, and so will return to the process that started us off in the first place.

Once the shim portion of the application kicks off the second process, we end up back in Main where we find that we do have our AppDomainManager setup properly.  Before we can start the application however, we need to strip the .exe name from args[0] (remember from above, it got there because we used Environment.CommandLine to start the second process).  Once we've dropped the first argument, we can invoke our application's real Main, which is named StartApplication().  From there on, the logic of setting up the application is gone, and you can write code just as you would for any other application.