The Managed Hosting API

With v1.0 and v1.1 of the CLR, if you wanted to have much control over how the CLR was working under the covers, you needed to write an unmanaged host.  The unmanaged hosting API still exists with Whidbey (in fact, its gotten quite a few improvements of its own -- Dino's blog is a good place to look for information on unmanaged hosting), but we've also introduced a way to tweak various internal CLR knobs completely from within managed code, a sort of managed hosting API.

This API consists of many classes, but the most important one is the AppDomainManager.  Every process that hosts the CLR can have only one type of AppDomainManager, and every AppDomain will have one instance of that manager.  So if process A is using AppDomainManagerA and has three AppDomains (the default domain and two additional domains), there will be three instances of AppDomainManagerA in the process, one in each AppDomain.  Even if process A uses AppDomainManagerA, process B can still come along and use AppDomainManagerB, since they're in separate processes they can use separate AppDomainManagers.  However, process A cannot use both AppDomainManagerA and AppDomainManagerB ... it's got to pick one.

So what is an AppDomainManager anyway?  Basically, its just a class that derives from System.AppDomainManager.  The base AppDomainManager class exposes several methods and properties that are customization points for you to affect how the CLR is working.  If you want to customize a specific behavior, all you have to do is override the corresponding property or method.

Once you've created this class, you need to let the CLR know to use your AppDomainManager.  The first step is to put the assembly containing the AppDomainManager into the GAC.  (In beta 1, we didn't enforce that the AppDomainManager must be in the GAC, but this will be enforced when we ship the final version of Whidbey.)  Since the AppDomainManager will be customizing how the CLR itself works, including affecting security decisions, it also obviously needs to be fully trusted.  Normally anything in the GAC will have FullTrust anyway, so you probably don't have to do anything there.  One other caveat is that the AppDomainManager cannot reside in the same assembly as the entry point of your application.  Generally, its a good idea to put the AppDomainManager and related types in a separate assembly, which will help avoid that problem.

Now that you've got the AppDomainManager in the GAC, there are three methods of telling the CLR to use it:

  • Environment Variables:
    • APPDOMAIN_MANAGER_ASM is the assembly name of the assembly containing the AppDomainManager
    • APPDOMAIN_MANAGER_TYPE is the type name (including namespace) of the AppDomainManager type
  • Registry Keys:
    • HKLM (or HKCU)\Software\Microsoft\.NETFramework\APPDOMAIN_MANAGER_ASM
    • HKLM (or HKCU)\Software\Microsoft\.NETFramework\APPDOMAIN_MANAGER_TYPE
  • Unmanaged Hosting API:
    • ICLRControl::SetAppDomainManagerType

The easiest of these is probably the environment variables, which are easy to set from the command line, and only affect the process tree where they are set.  The registry settings will affect every application running against the Whidbey version of the CLR on the machine; using the unmanaged hosting API requires you to create an unmanaged host.

So now that we know what an AppDomainManager is and how to let the CLR know that we want to use one, what are the customization points that it provides?  In the Whidbey Beta 1 version of AppDomainManager they are:

Name Property/Method Use

CreateDomain

Method Called when a domain is being created (via a call to AppDomain.CreateDomain for instance)
InitializeNewDomain Method First method called in a new AppDomain
ApplicationActivator Property Get the ApplicationActivator that will hook ClickOnce application activation
EntryAssembly Property Get the entry assembly of the application (for instance when the user calls Assembly.GetEntryAssembly)
HostExecutionContextManager Property Get the HostExecutionContextManager that hooks various hosting/ security context calls made by the CLR
HostSecurityManager Property Get the HostSecurityManager that hooks various CLR security policy decisions
InitializationFlags Property Flags that enable communication with the unmanaged CLR host

As you can see, there are a few methods that hook AppDomain creation and setup, and then several more properties that expose helper manager objects, which can hook more specific hosting areas.

Probably the easiest way to figure out which extension points the CLR is using is to write an EchoAppDomainManager that overrides each property and method, and simply prints out all the calls to that method to the console, then forwards the call on to the default AppDomainManager.  Here's a snippet of what that code would look like:

using System;
using System.Reflection;
using System.Runtime.Hosting;
using System.Security;
using System.Security.Policy;
using System.Threading;

namespace AppDomainManagers
{
    public sealed class EchoAppDomainManager : AppDomainManager
    {
        private void Trace(string location)
        {
            Console.WriteLine("AppDomain: {0}, EchoAppDomainManager::{1}", AppDomain.CurrentDomain.FriendlyName, location);
        }

        public EchoAppDomainManager() : base()
        {
            Trace(".ctor");
            return;
        }
        
        public override AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup appDomainInfo)
        {
            Trace("CreateDomain");
            return base.CreateDomain(friendlyName, securityInfo, appDomainInfo);
        }
        
        //
        // .... the other methods and properties here ...
        //

        public override HostSecurityManager HostSecurityManager
        {
            get
            {
                this.Trace("HostSecurityManager");
                return new EchoHostSecurityManager();
            }
        }
    }
}

I'll run a HelloWorld application with this AppDomainManager set ... however in order to make the output more interesting, I'm going to have to program print Hello World in both the default domain and in a second AppDomain that it creates itself.  The code for this program is:

using System;

public class HelloWorld
{
    public static void Main()
    {
        Console.WriteLine("In HelloWorld::Main");

        // say hello in this domain
        new HelloWorldWorker().SayHello();

        // create a new domain and say hello over there
        AppDomain newDomain = AppDomain.CreateDomain("Second AppDomain");
        HelloWorldWorker remote = newDomain.CreateInstanceAndUnwrap(typeof(HelloWorldWorker).Assembly.GetName().Name, typeof(HelloWorldWorker).Name) as HelloWorldWorker;
        remote.SayHello();
        return;
    }
}

public class HelloWorldWorker : MarshalByRefObject
{
    public void SayHello()
    {
        AppDomainManager currentManager = AppDomain.CurrentDomain.DomainManager;
        if(currentManager == null)
            Console.WriteLine("No AppDomainManager");
        else
            Console.WriteLine("AppDomainManager: " + currentManager.GetType().Name);

        Console.WriteLine("Hello World");
    }
}

OK ... now to try this out.  I'll compile my AppDomainManager into an assembly named AppDomainManagers.dll and place it in the GAC.  Then I'll setup the environment variables to tell the CLR to use the manager, and finally run my HelloWorld application.

D:\>set APPDOMAIN_MANAGER_ASM=AppDomainManagers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2674e92927c46c9e, processorArchitecture=MSIL
D:\>set APPDOMAIN_MANAGER_TYPE=AppDomainManagers.EchoAppDomainManager

D:\>HelloWorld.exe
AppDomain: DefaultDomain, EchoAppDomainManager::.ctor
AppDomain: DefaultDomain, EchoAppDomainManager::InitializeNewDomain
AppDomain: DefaultDomain, EchoAppDomainManager::HostExecutionContextManager
AppDomain: DefaultDomain, EchoAppDomainManager::HostSecurityManager
In HelloWorld::Main
AppDomainManager: EchoAppDomainManager
Hello World
AppDomain: HelloWorld.exe, EchoAppDomainManager::CreateDomain
AppDomain: HelloWorld.exe, EchoAppDomainManager::HostSecurityManager
AppDomain: Second AppDomain, EchoAppDomainManager::.ctor
AppDomain: Second AppDomain, EchoAppDomainManager::InitializeNewDomain
AppDomain: Second AppDomain, EchoAppDomainManager::HostExecutionContextManager
AppDomain: Second AppDomain, EchoAppDomainManager::HostSecurityManager
AppDomain: Second AppDomain, EchoAppDomainManager::HostExecutionContextManager
AppDomainManager: EchoAppDomainManager
Hello World
HelloWorld.exe

D:\>set APPDOMAIN_MANAGER_ASM=
D:\>set APPDOMAIN_MANAGER_TYPE=

From this output, we can see that the first thing that happens is that the AppDomainManager gets created in the default domain, and its InitializeNewDomain method is called.  (There's no CreateDomain call for the default domain ... since the AppDomainManager needs that domain to execute in).  Next the CLR asks for the HostExecutionContextManager and HostSecurityManager that the AppDomainManager will be providing for this domain.  But what's really interesting is that all of this occurred before Main started running.

Main does its thing, and then tries to create a second domain.  The call to CreateDomain occurs in the default AppDomain since the AppDomainManager participates in the creation process via this extension point.  Once the domain is created, a very similar sequence of properties and methods are called to the ones that were called in the default domain.  First we create a new AppDomainManager, then we initialize the domain, grab the host security and execution context managers, and run the code.

You can see from this output that the first type loaded into any domain is the AppDomainManager type, and the first two methods that run in every domain are the AppDomainManager constructor and InitializeNewDomain methods.

Tomorrow: A closer look at CreateDomain and InitializeNewDomain.