.NET Application Domain Internals

Abstract
 
In this article, you"ll drill deeper into the details of how an assembly is hosted by the CLR and will understand the relationship among Application Domains (appdomain) and processes. An appdomain in a nutshell is a segment within a given process that hosts a set of related .NET assemblies. In addition to that, this article also explores manipulation with currently running processes.
 
Process
 
A process is a fixed, safe boundary for a running program and operating system level concept used to describe a set of resources and the necessary memory allocations used by a running application. The operating creates a separate and isolated process for each executable loaded into memory. Furthermore, in application isolation, the result is much more stable and robust in the runtime environment because the failure of one process does not affect the functioning of another process. Data in one process can't be directly accessed by another process unless you use a distributed API programming such as WCF, COM+, and Remoting.
 
Every Windows process is assigned a unique process identifier (PID) and may be independently loaded and unloaded by the OS. You can view the various running processes of the Windows OS using the Task Manager as in the following:
 
processes
 
Every Windows process contains an initial thread that is the entry point (from Windows) for the application. Formally speaking, a thread is a path of execution within a process. Processes that contain a single primary thread of execution are considered to be thread-safe.
 
Process in Depth
 
The System.Diagonostic namespace defines a number of types that allow you to programmatically interact with processes and various other manipulations such as Performance Counters and event logs.
 
To illustrate the process of manipulating a Process object, assume you have a console application that displays all the currently running processes in the system.
  1. using System;  
  2. using System.Diagnostics;  
  3.   
  4. namespace ProcessDemo  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Process[] p = Process.GetProcesses("system-machine");  
  11.   
  12.             foreach (Process a in p)  
  13.             {  
  14.                 Console.WriteLine("Current Running Processes\n");  
  15.                 string str = string.Format("PID::{0} \t Name::{1}",a.Id,a.ProcessName);  
  16.                 Console.WriteLine(str);  
  17.                 Console.ReadKey();    
  18.             }  
  19.         }  
  20.     }  
  21. }  
You will see the PID and names for all the processes on your local computer as in the following:
 
running pid
 
In addition to obtaining a full list of all running processes on a given machine the GetProcessById() method allows you to obtain a single Process object via associated PID.
  1. using System;  
  2. using System.Diagnostics;  
  3.   
  4. namespace ProcessDemo  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Console.Write("Enter Process ID::");  
  11.             string pid = Console.ReadLine();   
  12.             Process p = null;  
  13.             try  
  14.             {  
  15.                 p = Process.GetProcessById(int.Parse(pid));  
  16.             }  
  17.             catch(Exception)  
  18.             {  
  19.                 Console.WriteLine("PID not Found");  
  20.             }  
  21.    
  22.             Console.WriteLine("Threds used by: {0}",p.ProcessName);  
  23.             ProcessThreadCollection ptc = p.Threads;   
  24.             foreach (ProcessThread a in ptc)  
  25.             {  
  26.                 Console.WriteLine("Current Running Processes\n");  
  27.                 string str = string.Format("PID::{0} \t Start Time::{1}",a.Id,a.StartTime.ToShortTimeString());  
  28.                 Console.WriteLine(str);  
  29.                 Console.ReadKey();    
  30.             }  
  31.         }  
  32.     }  
  33. }  
When you run your company, you can now enter the PID of any process on your machine and threads used in the process as in the following:
 
enter pid
 
The following sample examines the Start() method. This method provides a way to programmatically launch and terminate a process.
  1. using System;  
  2. using System.Diagnostics;  
  3.   
  4. namespace ProcessDemo  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Process p = null;  
  11.             try  
  12.             {  
  13.                 p = Process.Start("chrome.exe","www.google.com");  
  14.             }  
  15.             catch(Exception)  
  16.             {  
  17.                 Console.WriteLine("Error!!!");  
  18.             }  
  19.             Console.WriteLine("Process Start: {0}",p.ProcessName);  
  20.             Console.ReadKey();   
  21.         }  
  22.     }  
  23. }  
Application Domain
 
An Application Domain is a logical container for a set of assemblies in which an executable is hosted. As you have seen, a single process may contain multiple Application Domains, each of which is hosting a .NET executable. The first appdomain created when the CLR is initialized is called the default AppDomain and this default one is destroyed when the Windows process is terminated. Here are some specific features offered by an AppDomain:
  • An AppDomain can be independently secured
     
    When an appdomain is created, it can have a permission set applied to it that determines the maximum rights granted to the assemblies running in the AppDomain that ensures the code cannot be corrupted.
     
  • An AppDomain can be unloaded
     
    The CLR doesn't endorse the ability to unload a single assembly from an AppDomain. However, the CLR will notify to unload the entire currently contained assemblies from an appdomain.
     
  • Independently configured
     
    An AppDomain can have a cluster of configuration settings associated with it, for instance how the CLR loads assemblies into the appdomain, searches the path, and does loader optimization.
     
  • No mutual intervention by multiple appdomains
     
    When code in an AppDomain creates an object, it is not allowed to live beyond the lifetime of the AppDomain. The code in another AppDomain can access another object only by Marshal by reference or Marshal by value. This enforces a clean separation because code in one appdomain can't have a direct reference to an object created by another code in a different appdomain.
     
  • Performance
     
    Application Domains are less expensive thus the CLR is able to load and unload an Application Domain much faster than a formal process and that improves the performance.
The following image shows a single Windows process that has one CLR COM server running in it. This CLR is currently managing two Application Domains. Each appdomain has its own Heap and has a record of which type has been accessed since the appdomain was created. Apart from that, each Application Domain has some Assemblies loaded into it. AppDomain #1 (the default) has three assemblies and AppDomain #2 has two assemblies loaded: xyz.dll and System.dll.
 
So the entire purpose of An Application Domain is to provide isolation. The CLR needs to be able to unload an appdomain and free up all of its resources without adversely affecting any other appdomain.
 
app domain
 
System.AppDomain Class
 
The AppDomain class is used to create and terminate Application Domains, load and unload assemblies and types, and enumerates assemblies and threads in a domain. The following table shows some useful methods of the AppDomain class:
 
Methods Description
CreateDomain() It allows us to create a new Application Domain.
CreateInstance() Creates an instance of the type in an external assembly.
ExecuteAssembly() It executes a *.exe assembly in the Application Domain.
Load() This method dynamically loads an assembly into the current app domain.
UnLoad() It allows us to unload a specified AppDomain within a given process.
GetCurrentThread() Returns the ID of the active thread in the current Application Domain.
 
In addition, the AppDomain class also defines a set of properties that can be useful when you wish to monitor the activity of a given Application Domain.
 
Properties Description
CurrentDomain Gets the Application Domain for the currently executing thread.
FriendlyName Gets the friendly name of the current Application Domain.
SetupInformation Get the configuration details for a given Application Domain.
BaseDirectory Gets the directory path that the assembly resolver uses to probe for assemblies.
 
The following sample shows that a created assembly is called from another Application Domain. So, first, create a console application AppDomainTest. In the main() add a Console.WriteLine() so that you can see when this method is called.
  1. using System;  
  2.   
  3. namespace AppDomainTest  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             // Main assembly that is called from another AppDomain  
  10.             Console.WriteLine("AppDomainTest in new created Domain '{0}' called"  
  11.                       , AppDomain.CurrentDomain.FriendlyName);  
  12.             Console.WriteLine("ID of the Domain '{0}'"  
  13.                      , AppDomain.CurrentDomain.Id);  
  14.             Console.ReadKey();   
  15.         }  
  16.     }  
  17. }  
Then create a second project named DemoTest. Here first, display the name of the current domain using the property FriendlyName. With the CreateDomain() method, a new Application Domain with the friendly name New AppDomain is created. Then load the assembly AppDomainTest.exe into the new domain and call the Main() method by calling ExecuteAssembly().
  1. Using System;  
  2. //add a reference to AppDoaminTest.exe  
  3. namespace DemoTest  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             AppDomain d1 = AppDomain.CurrentDomain;  
  10.             Console.WriteLine(d1.FriendlyName);  
  11.   
  12.             AppDomain d2 = AppDomain.CreateDomain("New AppDomain");  
  13.             d2.ExecuteAssembly("AppDomainTest.exe");   
  14.         }  
  15.     }  
  16. }  
When you compile the DemoTest project, first the Current domain friendly name will be displayed followed by the called assembly as in the following:
 
appdomaintest
 
Loading Assemblies into Custom Application Domain
 
The CLR will always load assemblies into the default Application Domain when required. If you wanted to manually load assemblies into an Application Domain, then you can do this using the AppDomain.Load() method. Here, suppose you want to load a previously created library, TestLib.dll, into a secondary Application Domain.
  1. using System;  
  2. using System.IO;  
  3. using System.Linq;  
  4.   
  5. namespace DemoTest  
  6. {  
  7.     class Program  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.     
  12.             AppDomain newDomain = AppDomain.CreateDomain("New AppDomain");  
  13.   
  14.             try  
  15.             {  
  16.                 newDomain.Load("TestLib");   
  17.             }  
  18.             catch (FileNotFoundException)  
  19.             {  
  20.                 Console.WriteLine("Not Found");    
  21.             }  
  22.             ListAssemblies(newDomain);  
  23.             Console.ReadKey();   
  24.         }  
  25.   
  26.         static void ListAssemblies(AppDomain ad)  
  27.         {  
  28.             var la = from a in ad.GetAssemblies()  
  29.                      orderby a.GetName().Name  
  30.                      select a;  
  31.   
  32.             Console.WriteLine("Assemblies Loaded {0}\n",ad.FriendlyName);    
  33.   
  34.             foreach(var a in la)  
  35.             {  
  36.                 Console.WriteLine("Name:: {0}:", a.GetName().Name);  
  37.                 Console.WriteLine("Version:: {0}:\n", a.GetName().Version);  
  38.             }  
  39.         }  
  40.     }  
  41. }  
This time the output of the previous program is as in the following:
 
assemblies
 

Summary

 
The purpose of this article is to examine how a .NET executable image is hosted by the .NET platform. As you have seen, a single process can host multiple Application Domains, each of which is capable of hosting and executing any number of related assemblies.