
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
434 C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S
Starting and Stopping Processes Programmatically
The final aspects of the System.Diagnostics.Process type examined here are the Start() and Kill() methods. As you can gather by their names, these members provide a way to programmatically launch and terminate a process, respectively. For example, ponder the static StartAndKillProcess() helper method:
public static void StartAndKillProcess()
{
// Launch Internet Explorer.
Process ieProc = Process.Start("IExplore.exe", "www.intertechtraining.com");
Console.Write("--> Hit enter to kill {0}...", ieProc.ProcessName);
Console.ReadLine();
// Kill the iexplorer.exe process. try
{
ieProc.Kill();
}
catch{} // In case the user already killed it...
}
The static Process.Start() method has been overloaded a few times, however. At minimum, you will need to specify the friendly name of the process you wish to launch (such as Microsoft Internet Explorer). This example makes use of a variation of the Start() method that allows you to specify any additional arguments to pass into the program’s entry point (i.e., the Main() method).
The Start() method also allows you to pass in a System.Diagnostics.ProcessStartInfo type to specify additional bits of information regarding how a given process should come to life. Here is the formal definition of ProcessStartInfo (see the .NET Framework 2.0 SDK documentation for full details):
public sealed class System.Diagnostics.ProcessStartInfo : object
{
public ProcessStartInfo();
public ProcessStartInfo(string fileName);
public ProcessStartInfo(string fileName, string arguments); public string Arguments { get; set; }
public bool CreateNoWindow { get; set; }
public StringDictionary EnvironmentVariables { get; } public bool ErrorDialog { get; set; }
public IntPtr ErrorDialogParentHandle { get; set; } public string FileName { get; set; }
public bool RedirectStandardError { get; set; } public bool RedirectStandardInput { get; set; } public bool RedirectStandardOutput { get; set; } public bool UseShellExecute { get; set; } public string Verb { get; set; }
public string[] Verbs { get; }
public ProcessWindowStyle WindowStyle { get; set; } public string WorkingDirectory { get; set; } public virtual bool Equals(object obj);
public virtual int GetHashCode(); public Type GetType();
public virtual string ToString();
}


436 C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S
Table 13-4. (Continued)
Member |
Meaning in Life |
Unload() |
This is another static method that allows you to unload a specified |
|
AppDomain within a given process. |
BaseDirectory |
This property returns the base directory used to probe for dependent |
|
assemblies. |
CreateInstance() |
This method creates an instance of a specified type defined in |
|
a specified assembly file. |
ExecuteAssembly() |
This method executes an assembly within an application domain, |
|
given its file name. |
GetAssemblies() |
This method gets the set of .NET assemblies that have been loaded into |
|
this application domain (COM-based or C-based binaries are ignored). |
Load() |
This method is used to dynamically load an assembly into the current |
|
application domain. |
|
|
In addition, the AppDomain type also defines a small set of events that correspond to various aspects of an application domain’s life cycle, as shown in Table 13-5.
Table 13-5. Events of the AppDomain Type
Event |
Meaning in Life |
AssemblyLoad |
Occurs when an assembly is loaded |
AssemblyResolve |
Occurs when the resolution of an assembly fails |
DomainUnload |
Occurs when an AppDomain is about to be unloaded |
ProcessExit |
Occurs on the default application domain when the default application |
|
domain’s parent process exits |
ResourceResolve |
Occurs when the resolution of a resource fails |
TypeResolve |
Occurs when the resolution of a type fails |
UnhandledException |
Occurs when an exception is not caught by an event handler |
|
|
Enumerating a Process’s AppDomains
To illustrate how to interact with .NET application domains programmatically, assume you have
a new C# console application named AppDomainManipulator that defines a static method named
PrintAllAssembliesInAppDomain(). This helper method makes use of AppDomain.GetAssemblies() to obtain a list of all .NET binaries hosted within the application domain in question.
This list is represented by an array of System.Reflection.Assembly types, and thus you are required to use the System.Reflection namespace (see Chapter 12). Once you acquire the assembly array, you iterate over the array and print out the friendly name and version of each module:
public static void PrintAllAssembliesInAppDomain(AppDomain ad)
{
Assembly[] loadedAssemblies = ad.GetAssemblies(); Console.WriteLine("***** Here are the assemblies loaded in {0} *****\n",
ad.FriendlyName);
foreach(Assembly a in loadedAssemblies)
{
Console.WriteLine("-> Name: {0}", a.GetName().Name); Console.WriteLine("-> Version: {0}\n", a.GetName().Version);
}

C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S |
437 |
Now let’s update the Main() method to obtain a reference to the current application domain before invoking PrintAllAssembliesInAppDomain(), using the AppDomain.CurrentDomain property.
To make things a bit more interesting, notice that the Main() method launches a Windows Forms message box to force the CLR to load the System.Windows.Forms.dll, System.Drawing.dll, and System.dll assemblies (so be sure to set a reference to these assemblies and update your using statements appropriately):
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing AppDomain app *****\n");
// Get info for current AppDomain.
AppDomain defaultAD= AppDomain.CurrentDomain; MessageBox.Show("Hello"); PrintAllAssembliesInAppDomain(defaultAD);
Console.ReadLine();
}
Figure 13-6 shows the output (your version numbers may differ).
Figure 13-6. Enumerating assemblies within the current application domain
Programmatically Creating New AppDomains
Recall that a single process is capable of hosting multiple AppDomains. While it is true that you will seldom (if ever) need to manually create AppDomains in code, you are able to do so via the static CreateDomain() method. As you would guess, AppDomain.CreateDomain() has been overloaded a number of times. At minimum, you will specify the friendly name of the new application domain, as shown here:
static void Main(string[] args)
{
...
// Make a new AppDomain in the current process.
AppDomain anotherAD = AppDomain.CreateDomain("SecondAppDomain"); PrintAllAssembliesInAppDomain(anotherAD);
Console.ReadLine();
}

438 C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S
Now, if you run the application again (see Figure 13-7), notice that the System.Windows.Forms.dll, System.Drawing.dll, and System.dll assemblies are only loaded within the default application domain. This may seem counterintuitive if you have a background in traditional Win32 (as you might suspect, both application domains have access to the same assembly set). Recall, however, that an assembly loads into an application domain, not directly into the process itself.
Figure 13-7. A single process with two application domains
Next, notice how the SecondAppDomain application domain automatically contains its own copy of mscorlib.dll, as this key assembly is automatically loaded by the CLR for each and every application domain. This begs the question, “How can I programmatically load an assembly into an application domain?” Answer: with the AppDomain.Load() method (or, alternatively, AppDomain. ExecuteAssembly()). Assuming you have copied CarLibrary.dll to the application directory of AppDomainManipulator.exe, you may load CarLibrary.dll into the SecondAppDomain AppDomain as so:
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing AppDomain app *****\n");
...
// Load CarLibrary.dll into the new AppDomain.
AppDomain anotherAD = AppDomain.CreateDomain("SecondAppDomain"); anotherAD.Load("CarLibrary"); PrintAllAssembliesInAppDomain(anotherAD);
Console.ReadLine();
}
To solidify the relationship between processes, application domains, and assemblies, Figure 13-8 diagrams the internal composition of the AppDomainManipulator.exe process just constructed.

C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S |
439 |
Figure 13-8. The AppDomainManipulator.exe process under the hood
Programmatically Unloading AppDomains
It is important to point out that the CLR does not permit unloading individual .NET assemblies. However, using the AppDomain.Unload() method, you are able to selectively unload a given application domain from its hosting process. When you do so, the application domain will unload each assembly in turn.
Recall that the AppDomain type defines a small set of events, one of which is DomainUnload. This event is fired when a (nondefault) AppDomain is unloaded from the containing process. Another event of interest is the ProcessExit event, which is fired when the default application domain is unloaded from the process (which obviously entails the termination of the process itself). Thus, if you wish to programmatically unload anotherAD from the AppDomainManipulator.exe process and be notified when the associated application domain is torn down, you are able to write the following event logic:
static void Main(string[] args)
{
...
//Hook into DomainUnload event. anotherAD.DomainUnload +=
new EventHandler(anotherAD_DomainUnload);
//Now unload anotherAD.
AppDomain.Unload(anotherAD);
}
Notice that the DomainUnload event works in conjunction with the System.EventHandler delegate, and therefore the format of anotherAD_DomainUnload() takes the following arguments:
public static void anotherAD_DomainUnload(object sender, EventArgs e)
{
Console.WriteLine("***** Unloaded anotherAD! *****\n");
}
If you wish to be notified when the default AppDomain is unloaded, modify your Main() method to handle the ProcessEvent event of the default application domain:


C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S |
441 |
Context-Agile and Context-Bound Types
.NET types that do not demand any special contextual treatment are termed context-agile objects. These objects can be accessed from anywhere within the hosting AppDomain without interfering with the object’s runtime requirements. Building context-agile objects is a no-brainer, given that you simply do nothing (specifically, you do not adorn the type with any contextual attributes and do not derive from the System.ContextBoundObject base class):
// A context-agile object is loaded into context 0. public class SportsCar{}
On the other hand, objects that do demand contextual allocation are termed context-bound objects, and they must derive from the System.ContextBoundObject base class. This base class solidifies the fact that the object in question can function appropriately only within the context in which it was created. Given the role of .NET context, it should stand to reason that if a context-bound object were to somehow end up in an incompatible context, bad things would be guaranteed to occur at the most inopportune times.
In addition to deriving from System.ContextBoundObject, a context-sensitive type will also be adorned by a special category of .NET attributes termed (not surprisingly) context attributes. All context attributes derive from the System.Runtime.Remoting.Contexts.ContextAttribute base class:
public class System.Runtime.Remoting.Contexts.ContextAttribute : Attribute, IContextAttribute, IContextProperty
{
public ContextAttribute(string name); public string Name { virtual get; } public object TypeId { virtual get; } public virtual bool Equals(object o);
public virtual void Freeze(System.Runtime.Remoting.Contexts.Context newContext); public virtual int GetHashCode();
public virtual void GetPropertiesForNewContext( System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg);
public Type GetType();
public virtual bool IsContextOK( System.Runtime.Remoting.Contexts.Context ctx, System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg);
public virtual bool IsDefaultAttribute(); public virtual bool IsNewContextOK(
System.Runtime.Remoting.Contexts.Context newCtx); public virtual bool Match(object obj);
public virtual string ToString();
}
Given that the ContextAttribute class is not sealed, it is possible for you to build your own custom contextual attribute (simply derive from ContextAttribute and override the necessary virtual methods). Once you have done so, you are able to build a custom piece of software that can respond to the contextual settings.
■Note This book doesn’t dive into the details of building custom object contexts; however, if you are interested in learning more, check out Applied .NET Attributes (Apress, 2003).

442 C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S
Defining a Context-Bound Object
Assume that you wish to define a class (SportsCarTS) that is automatically thread-safe in nature, even though you have not hard-coded thread synchronization logic within the member implementations. To do so, derive from ContextBoundObject and apply the [Synchronization] attribute as follows:
using System.Runtime.Remoting.Contexts;
//This context-bound type will only be loaded into a
//synchronized (hence thread-safe) context. [Synchronization]
public class SportsCarTS : ContextBoundObject {}
Types that are attributed with the [Synchronization] attribute are loaded into a thread-safe context. Given the special contextual needs of the MyThreadSafeObject class type, imagine the problems that would occur if an allocated object were moved from a synchronized context into a nonsynchronized context. The object is suddenly no longer thread-safe and thus becomes a candidate for massive data corruption, as numerous threads are attempting to interact with the (now thread-volatile) reference object. To ensure the CLR does not move SportsCarTS objects outside of a synchronized context, simply derive from ContextBoundObject.
Inspecting an Object’s Context
Although very few of the applications you will write will need to programmatically interact with context, here is an illustrative example. Create a new console application named ContextManipulator. This application defines one context-agile class (SportsCar) and a single context-bound type (SportsCarTS):
using System.Runtime.Remoting.Contexts; // For Context type. using System.Threading; // For Thread type.
//SportsCar has no special contextual
//needs and will be loaded into the
//default context of the app domain. public class SportsCar
{
public SportsCar()
{
//Get context information and print out context ID.
Context ctx = Thread.CurrentContext; Console.WriteLine("{0} object in context {1}",
this.ToString(), ctx.ContextID);
foreach(IContextProperty itfCtxProp in ctx.ContextProperties) Console.WriteLine("-> Ctx Prop: {0}", itfCtxProp.Name);
}
}
//SportsCarTS demands to be loaded in
//a synchronization context.
[Synchronization]
public class SportsCarTS : ContextBoundObject
{
public SportsCarTS()
{
