Customizing the CLR Part 2, Page 2
Previous Page

Building Custom AppDomain Managers

The AppDomainManager Class

First, a warning. When you read about the AppDomainManager class in the Microsoft documentation, don't accidently focus on a different AppDomainManager, Microsoft.VisualStudio.Tools.Applications.Runtime.AppDomainManager. This is an internal sealed class for use by Office tools. We need System.AppDomainManager, which, as we have just said, is new for .NET 2.0. Let's take a look at the methods we shall use first.

It would be reasonable to assume that the CreateDomain method is used to create AppDomains, but this is not the case. AppDomains are, in fact, created by the AppDomainHelper class. While this may seem counterintuitive, it is an important design pattern. .NET code may call CreateDomain, but our AppDomainManager might choose to override this request. For example, our AppDomainManager might maintain a pool of existing AppDomains. Based on some criteria, perhaps security credentials, our manager might decide to return a reference to an existing AppDomain when client code requests the creation of a new one. If the code in CreateDomain decides that the creation of a new AppDomain is required, that code calls AppDomainHelper, which actually creates the new AppDomain and returns a reference to it.

Our custom AppDomainManager must also make security decisions. The CreateDomain method expects a reference to an Evidence object (System.Security.Policy.Evidence)  as one of its arguments. The security information carried by this object will establish the so-called "top-of-stack" permission set, which will place limits on what code loaded by the AppDomain is permitted to do. Let's take a look at code for a test AppDomainManager, which doesn't add any new functionality but illustrates how a custom AppDomainManager is built and applied.

    1 using System;

    2 using System.Reflection;

    3 using System.Runtime.Hosting;

    4 using System.Security;

    5 using System.Security.Policy;

    6 using System.Threading;

    7 

    8 namespace AppDomainManager02

    9 {

   10     public sealed class AppDomainManagerTest : AppDomainManager

   11     {

   12         // (1)

   13         // Only fully-trusted code can be loaded in an AppDomainManager constructor

   14         public AppDomainManagerTest()

   15             : base()

   16         {

   17             Report(".ctor", ConsoleColor.Red );

   18             return;

   19         }

   20 

   21         // (2) We don't do any work, just pass the request to the base class

   22         public override AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup appDomainInfo)

   23         {

   24             Report("CreateDomain", ConsoleColor.DarkGreen);

   25             return base.CreateDomain(friendlyName, securityInfo, appDomainInfo);

   26         }

   27 

   28         // (3) A CLR host can provide a custom security manager to fine-tune the

   29         // security behavior of an AppDomain. This propery provides programmatic access

   30         // to the HostSecurityManager. In this example, we simply return a reference

   31         // to the a new instance of the default HostSecurityManager.

   32         public override HostSecurityManager HostSecurityManager

   33         {

   34             get

   35             {

   36                 Report("HostSecurityManager", ConsoleColor.DarkYellow);

   37                 return new HostSecurityManager();

   38             }

   39         }

   40 

   41         /*

   42         // CreateAppDomainHelper actually creates a new AppDomain

   43         // this allows CreateDomain to either return an exisiting AppDomain from a

   44         // pool or call CreateDomainHelper to create an actual new AppDomain

   45         protected static AppDomain CreateDomainHelper (

   46                                                         string friendlyName,

   47                                                         Evidence securityInfo,

   48                                                         AppDomainSetup appDomainInfo)

   49           {

   50 

   51           }

   52 

   53         public AppDomainManagerInitializationOptions InitializationFlags { get; set; }

   54 

   55         public override void InitializeNewDomain(AppDomainSetup appDomainInfo)

   56         {

   57             Console.Write("Initialize new domain called:  ");

   58             Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);

   59             InitializationFlags =

   60                 AppDomainManagerInitializationOptions.RegisterWithHost;

   61         }

   62 

   63 

   64         */

   65 

   66 

   67         // Report() is only for learning purposes

   68         // Report() displays the current AppDomain location for whatever thread calls it

   69         private void Report(string location, ConsoleColor color)

   70         {

   71             ConsoleColor original = Console.ForegroundColor;

   72             Console.ForegroundColor = color;

   73             Console.WriteLine("AppDomain: {0}, AppDomainManagerTest::{1}", AppDomain.CurrentDomain.FriendlyName, location);

   74             Console.ForegroundColor = original;

   75         }

   76         private void Report(string location)

   77         {

   78             Console.WriteLine("AppDomain: {0}, AppDomainManagerTest::{1}", AppDomain.CurrentDomain.FriendlyName, location);

   79         }

   80     }

   81 }

   82 

 

Figure 5.

Section (1) is the constructor. In this example, we only note the fact that the constructor executes. If you want to load assemblies in the constructor, it is imperative that these assemblies be fully-trusted, and the CLR will insist on it. To be loaded in the AppDomain constructor, an assembly must either be in the GAC or be marked as fully trusted in an array of strong-names provided by the invoking code.

Section (2) is where we actually create the new managed AppDomain object. This method will be called by the CLR when a request for a new AppDomain is made by some executing managed code. We have not yet discussed how the CLR knows to do this; this is coming up shortly.

In section (3) we provide a HostSecurityManager object when the CLR requests one. We could fine-tune our security be providing a custom HostSecurityManager.

The dll containing our AppDomainManager must be strongly named. Clearly, .NET needs to trust code that controls all security within all AppDomains. A key file is included in the zipped source files; you may wish to use this file for consistency.

As is usually the case, we will need a test platform to see if our code works. In this case we will use a "HelloWorld" program, to keep things as simple as practical. We will integrate a custom AppDomainManager with our progammable CodeDom application later on. Here is the source for our HelloWorld test:

    1 using System;

    2 

    3 public class HelloWorld

    4 {

    5     public static void Main()

    6     {

    7         Console.WriteLine("HelloWorld::Main() has begun.");

    8 

    9         // say hello in this domain

   10         new HelloWorldAux().HelloWorld();

   11 

   12         // create a new domain and say hello over there

   13         AppDomain newDomain = AppDomain.CreateDomain("Second AppDomain");

   14         HelloWorldAux remote = newDomain.CreateInstanceAndUnwrap(typeof(HelloWorldAux).Assembly.GetName().Name, typeof(HelloWorldAux).Name) as HelloWorldAux;

   15         remote.HelloWorld();

   16         return;

   17     }

   18 }

   19 

   20 public class HelloWorldAux : MarshalByRefObject

   21 {

   22     public void HelloWorld()

   23     {

   24         AppDomainManager currentManager = AppDomain.CurrentDomain.DomainManager;

   25         if (currentManager == null)

   26             Console.WriteLine("No Managed AppDomainManager");

   27         else

   28             Console.WriteLine("AppDomainManager: " + currentManager.GetType().Name);

   29 

   30         Console.WriteLine("Hola, mundo");

   31     }

   32 }

   33 

Figure 6.

Just as before, we must create a class in anticipation of launching it in a new AppDomain. In this case, the class is called HelloWorldAux.

Before we can test our application, we must find some way of telling the CLR to use our AppDomainManager. Ultimately, we will want to program our unmanaged host to specify the manager and obtain a reference to it. Alternatively, we could change entries in the Windows system registry so that the CLR will always load our custom manager. Obviously, this is not the wisesst choice for the early stages of testing. The last alternative is to set environment variables in a command console. Any CLR loaded up by a process launched from that window will use our custom AppDomainManager. Others will not.

A batch file to set the environment variables is included in the zipped example files. If you prefer typing it yourself, it looks like this:

SET APPDOMAIN_MANAGER_ASM=AppDomainManager02,Version=1.0.0.0,Culture=neutral, PublicKeyToken=931530800db9d27a,processorArchitecture=MSIL
SET APPDOMAIN_MANAGER_TYPE=AppDomainManager02.AppDomainManagerTest

Obviously, each of the SET commands must be on a single line. The value for the public key presumes that you use the strong key file included with the project files. If you create your own key file, you will need to substitute your new public key.

By default, the CLR will not probe for the AppDomainManager assembly. This means that there are only two choices. The assembly must be located in the same folder as the exe file, or the assembly must be placed into the GAC.

After you copy HelloWorld.exe and AppDomainManager02.dll into the desired folder, we are ready to test. Open a command prompt window and run the SET statements above or run the envvars.bat batch file. Then type "HelloWorld" and hit <enter> like you normally would. You should see something like this:

command line example

Figure 7.

Now we need to understand exactly what we are seeing. After Windows recognizes HelloWorld for a managed executable, it loads the CLR, which creates a default AppDomain. The CLR then creates an instance of our AppDomainManager for that new AppDomain, and we see the constructor in our object report its execution in red. The CLR then makes several requests for HostSecurityManager objects; we see these in yellow. The CLR then loads and runs HelloWorld.exe. We see the execution of Main(). Main() the reports the AppDomainManager for its AppDomain, the default AppDomain.

The code in Main() then requests the creation of a new AppDomain by calling the CreateDomain() method of the AppDomain class. Note that Main() is not calling our AppDomainManager. The CLR calls the CreateDomain method of the AppDomainManager in the default AppDomain, giving it a chance to specify the characteristics of the new AppDomain about to be created. Indeed, as mentioned our custom AppDomainManager might even decide not to grant the request for a new AppDomain, and return a reference to an appropriate existing AppDomain instead. The CreateDomain() method reports its execution in green in the console. Only after this, if our managed code requests it, does the CLR create a new AppDomain and load into it a new instance of our AppDomainManager class. We see the instantiation of this class in the red report in the console. Once again, the CLR requests HostSecurityManager objects. At this point, the new AppDomain is ready for use and the HelloWorld() method executes in the new AppDomain. It is worth nothing that the HelloWorldAux class must be loaded in both AppDomains, or there would be no way for the default AppDomain to create a proxy to run the code in the second domain. We may ultimately wish to consider alternatives to this duplication.

Congratulations on getting this code running. In the next installment, we will add some security features, making a real custom AppDomainManager, and integrate this into our unmanged host application.

Source Code

The source code for all examples can be found here .

 

About the author:

 

Dan Buskirk had been a research scientist for many years when he left the university labs to participate in a startup venture to design and build databases for medical science. Since that time he has managed a consulting practice specializing in database design and development. Dan balances this with training on Microsoft SQL Server, Microsoft Analysis Services, and .NET programming. His interests include mathematical methods for data mining and computing methods for advanced data categorization on Windows clusters.