4. The AppDomain Class
.NET represents app domains with the AppDomain
class, which provides numerous methods for loading assemblies, creating
objects from those assemblies, and configuring app domain security. You
can get hold of an object representing the app domain within which your
component code is currently running by accessing the static property CurrentDomain of the AppDomain class:
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Alternatively, you can call the GetDomain( ) static method of the Thread class, which also returns an AppDomain object representing the current domain:
AppDomain currentAppDomain;
currentAppDomain = Thread.GetDomain( );
Every app domain has a readable name. Use the FriendlyName read-only property of the AppDomain class to obtain the name of the app domain:
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Trace.WriteLine(currentAppDomain.FriendlyName);
4.1. The default app domain
Every unmanaged
process hosting .NET components is created by launching a .NET EXE
assembly, such as a console application, a Windows Forms application, or
a Windows Service application. Each such EXE has a Main( )
method, which is the entry point to the new app domain in the process.
When the EXE is launched, .NET creates a new app domain called the default app domain. The name of the default app domain is that of the hosting EXE assembly (such as MyApp.exe).
The default app domain cannot be unloaded, and it remains running
throughout the life of the hosting process. For diagnostic purposes, you
can verify whether your code executes in the default app domain using
the IsDefaultAppDomain( ) method of the AppDomain class:
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Debug.Assert(currentAppDomain.IsDefaultAppDomain( ));
When debugging inside Visual Studio 2005, the EXE assembly is actually
loaded in the VSHost process. Consequently, in a debug session of the MyApp.exe assembly, the default app domain's name will be MyApp.vshost.exe. |
4.2. Creating objects in app domains
The AppDomain class offers a few permutations of a CreateInstance( ) method that allows you to explicitly create a new instance of any type in the app domain. For example, one of the versions of CreateInstance( ) is called CreateInstanceAndUnwrap( ), defined as:
public object CreateInstanceAndUnwrap(string assemblyName, string typeName);
CreateInstanceAndUnwrap( ) accepts an assembly filename and a fully qualified type name. CreateInstanceAndUnwrap( ) then creates an instance of the type and returns an object representing it. Example 1 demonstrates CreateInstanceAndUnwrap( ).
When you specify an assembly name to any of the methods of AppDomain, the calling assembly must reference the assembly being specified. |
|
Example 1. Explicitly creating an object in the current app domain
//In the MyClassLibrary.dll assembly:
namespace MyNamespace
{
public class MyClass
{
public void TraceAppDomain( )
{
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Console.WriteLine(currentAppDomain.FriendlyName);
}
}
}
//In the MyApp.exe assembly:
using MyNamespace;
public class MyClient
{
static void Main( )
{
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Console.WriteLine(currentAppDomain.FriendlyName);
MyClass obj;
obj = (MyClass)currentAppDomain.CreateInstanceAndUnwrap("MyClassLibrary",
"MyNamespace.MyClass");
obj.TraceAppDomain( );
}
}
//Output:
MyApp.exe //Traces MyApp.vshost.exe when running in the debugger
|
In this example, a class called MyClass is defined in the MyNamespace namespace in the MyClassLibrary.dll class library assembly. MyClass provides the TraceAppDomain( ) method, which traces the name of its current app domain to the Console window. The client is in a separate EXE assembly called MyApp.exe. The client obtains its current AppDomain object and traces its name. When running outside the debugger, the trace yields MyApp.exe—the name of the default app domain. Next, instead of creating an instance of MyClass directly using new, the client calls CreateInstanceAndUnwrap( ), providing the assembly name and the fully qualified type name. When the client calls the TraceAppDomain( ) method on the new MyClass object, the object traces MyApp.exe to the Console window because it shares the app domain of the client (the default app domain, in this example).
The client can use CreateInstanceAndUnwrap( ) to create types defined in its own assembly, by providing its own assembly name. Note that CreateInstanceAndUnwrap( ) uses the default constructor of the object. To provide construction parameters you need to use another version of CreateInstanceAndUnwrap( ), which accepts an array of construction parameters:
public object CreateInstanceAndUnwrap(string assemblyName, string typeName,
object[] activationAttributes);
The client can also specify explicitly how to bind to the server assembly, using yet another overloaded version of CreateInstanceAndUnwrap( ).
Interacting with app domains is usually required by framework vendors who want to explicitly create new app domains and
load assemblies and types into them. I find that during conventional
development I need to interact with app domains only to configure
security policies or for advanced security purposes. |
|
4.3. Creating a new app domain
You typically create new
app domains for the same reasons you create processes in traditional
Windows development: to provide fault isolation and security isolation.
The AppDomain class provides the static CreateDomain( ) method, which allows you to create new app domains:
public static AppDomain CreateDomain(string friendlyName);
CreateDomain( ) creates a new app domain in the same process and returns an AppDomain object representing the new app domain. The new app domain must be given a new name when you call CreateDomain( ).
The AppDomain type is derived from MarshalByRefObject. Deriving from MarshalByRefObject allows .NET to pass a reference to the AppDomain object outside its app domain boundaries. When you create a new app domain using the CreateDomain( ) method, .NET creates a new app domain, retrieves a reference to the AppDomain object, and marshals it back to your current domain. |
|
Example 2 demonstrates how to create a new app domain and then instantiate a new object in the new app domain.
Example 2. Creating a new app domain and a new object in it
//In the MyClassLibrary.dll assembly:
namespace MyNamespace
{
public class MyClass : MarshalByRefObject
{
public void TraceAppDomain( )
{
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Console.WriteLine(currentAppDomain.FriendlyName);
}
}
}
//In the MyApp.exe assembly:
using MyNamespace;
public class MyClient
{
static void Main( )
{
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
Console.WriteLine(currentAppDomain.FriendlyName);
AppDomain newAppDomain;
newAppDomain = AppDomain.CreateDomain("My new AppDomain");
MyClass obj;
obj = (MyClass)newAppDomain.CreateInstanceAndUnwrap("MyClassLibrary",
"MyNamespace.MyClass");
obj.TraceAppDomain( );
}
}
//Output:
MyApp.exe //Or MyApp.vshost.exe when running in the debugger
My new AppDomain
|
Example 2 is similar to Example 1, with a few notable exceptions. The class MyClass is derived from MarshalByRefObject, so you can access it across app domains. The client traces its own app domain name (MyApp.exe) and then creates a new app domain using the CreateDomain( ) static method. As in Example 1, the client creates a new object of type MyClass in the new app domain and asks it to trace its app domain. When the program is executed, the name My
new
AppDomain is displayed in the console, confirming that the object is in the newly created app domain.
4.4. Unwrapping remote objects
You're probably wondering why the word Unwrap is appended to the CreateInstanceAndUnwrap( ) method. As already mentioned, accessing a remote object
is done through a proxy. For optimization purposes, .NET separates the
act of creating an object from the act of setting up a proxy on the
client side. This allows you to create a remote object and set up the
proxy later. The AppDomain class provides a set of CreateInstance( ) methods that create a new object but return a handle to the remote object in the form of ObjectHandle:
public virtual ObjectHandle CreateInstance(string assemblyName, string typeName);
ObjectHandle is defined in the System.Runtime.Remoting namespace. It implements the IObjectHandle interface:
public interface IObjectHandle
{
object Unwrap( );
}
public class ObjectHandle : MarshalByRefObject,IObjectHandle
{
public ObjectHandle(object obj);
public virtual object Unwrap( );
}
The Unwrap( )
method sets up the proxy on the client side. You can actually unwrap a
handle multiple times, either by the same client or by different
clients. Using the same object definitions as Example 2, here is how to unwrap an object handle:
AppDomain newAppDomain;
IObjectHandle handle;
MyClass obj;
newAppDomain = AppDomain.CreateDomain("My new AppDomain");
handle = newAppDomain.CreateInstance("MyClassLibrary","MyNamespace.MyClass");
//Only now a proxy is set up:
obj = (MyClass)handle.Unwrap( );
obj.TraceAppDomain( );
Typically, there is
no need to manually unwrap object handles. .NET provides the option for
the advanced case in which you want to pass the handle between clients
in different app domains, instead of the object itself. As a result, the
client can defer loading the assembly containing the object metadata (a
required step when setting up a proxy) until the client actually needs
to use the object.
5. The Host App Domain
.NET calls the app domain that contains the server object the host app domain. The host app domain
can be in the same process as the client app domain, in a different
process on the same machine, or on a different machine altogether. To
qualify as a host, the app domain must register itself as such with
.NET, letting .NET know which objects the host is willing to accept
remote calls on, and in what manner. Because .NET is aware of the host
only after registration, the host must be running before remote calls
are issued.
COM differed from .NET
in that if calls were made to a hosting process that was not running,
COM would launch it and let it host objects. New COM activation requests
would then be redirected to the already running process. COM could do
that because the Registry held the registration information regarding
which process to launch and because once the process was running, it
registered the objects it was hosting programmatically. .NET doesn't use
the Registry (hence the limitation). |
|
Both the client and the host app domains
require access to the server assembly. The client app domain needs the
server metadata to compile against, and at runtime .NET requires the
server assembly on the client side so it can reflect its metadata and
build a proxy. The IL code in the assembly isn't required at compile
time or at runtime on the client's side. The host app domain requires
the server assembly at runtime to create the hosted components and for
call-marshaling purposes. If the host is doing programmatic registration , that host must have access to the
server assembly at compile time as well.
Unless you explicitly create a new app domain in your own process (as in Example 2),
the host app domain will be in a different process, in the form of an
EXE assembly. You could put the server code in the same EXE as the host
and have the client application add a reference to the host directly.
However, that would make it more complicated for the client to use the
same remote type when other hosts are hosting that type. The common
solution to this problem is to separate the host, the server, and the
client to different assemblies: the host resides in an EXE assembly, the
server in a class library assembly, and the client application in a
different class library or EXE assembly. That way, the client can use
the metadata in the server class library at compile time and redirect
the remote calls to any host that loads the same server class library.
At runtime, the client loads the class library only to use its metadata;
again, it has no need for the code in it. The host assembly uses the
server assembly's metadata at compile time if it uses programmatic
object registration; at runtime, the host uses both the metadata and the
code in the server assembly to host the class library objects. Figure 4 depicts what the client and the host require of the server class library assembly at compile time and at runtime.