Section 10.1. Application Domains


10.1. Application Domains

All .NET components and applications require a managed environment to run in. However, the underlying operating system knows nothing about managed code; it provides processes only. Processes are also unaware that .NET exists; they provide raw elements such as memory, handle tables, and so on. Managed code therefore can't execute directly in the native operating system processthere is a need for a bridge between managed code and unmanaged code. The bridging link is a concept called an application domain, or app domain. You can think of the app domain as the .NET equivalent of a process, with one important difference: an app domain is built on top of the unmanaged process, and there is no requirement for one-to-one mapping between app domains and operating system processes. As a result, a single physical process can actually host multiple app domains (see Figure 10-1).

Figure 10-1. Processes, app domains, and assemblies


10.1.1. App Domains Versus Physical Processes

App domains are better perceived as logical processes, instead of real processes. The fact that a single physical process can host multiple app domains yields important benefits. The main reason why developers resorted to multiple processes in the past was to provide fault isolation. If all the components of an application and their clients are in the same process and a component has a fatal error that crashes the process, it brings down the entire application, including the clients. Similarly, if the client has a fatal error, the components go down with it. By distributing the clients and servers of an application to separate processes, an application achieves fault isolationin the event of a fault only the culprit process goes down, allowing you to handle the error or perform a graceful exit.

Another reason for distributing the components of an application across processes is security. Server objects are often called on to authenticate incoming client calls or to perform access control and authorization before allowing a given call to access a component. Having separate processes allows for separate security identifiers for each process and for the enforcement of authentication on cross-process calls. Unfortunately, however, there are significant penalties to using multiple processes:

  • Creating a new process is time-consuming, as is the disposal of an existing process.

  • Keeping a process running is expensive, both in terms of memory and of the resources the operating system allocates to each process. Having too many processes running can significantly degrade system performance.

  • Making a cross-process call involves a call penalty, because crossing a process boundary is very expensive compared to making a direct call. Cross-process calls rely on special mechanisms such as named pipes, sockets, and LPC/RPC.

  • Coding is more complexthe client's code for making a direct local call on an object is very different from that of making the same call on the object in a different process.

Compared with traditional unmanaged processes, .NET app domains can provide single-process performance with lower overhead. They can also provide the isolation and other benefits of multiple processes, even if they share the same physical process. You can start and shut down app domains independently of their hosting processes, and you can even debug them separately. For example, all ASP.NET web applications share the same physical worker process by default, but each web application is put in its own dedicated app domain. The time it takes to create or destroy an app domain is a fraction of that required for a physical process, and keeping an app domain alive is considerably cheaper. Furthermore, cross-app domain calls in the same process are faster than cross-process calls. .NET also maintains a strict security boundary between app domains, so objects in one app domain can't interfere with the objects (or data) in another, unless the objects agree to cooperate using .NET remoting.

In unmanaged C++, static variables are visible to all clients in the same process. In C#, each app domain gets its own separate set of static variables.


In the interest of fault isolation and security, each app domain loads and maintains its own set of assemblies. Consider, for example, the app domains in Figure 10-1. Because App Domain B and App Domain C require the class library Assembly 1, on Windows .NET loads Assembly 1 twice and gives each app domain its own copy. This allows clients in each app domain to interact with Assembly 1 independently of other clients in other app domains.

10.1.2. App Domains and the .NET Platform

The .NET runtime itself is a set of Windows DLLs, implemented in unmanaged C++. These DLLs provide the managed heap, garbage collector, JIT compiler, assembly resolver and loader, and all the other elements that make managed code possible. The app domain merely enables the assemblies it loads to access these services (see Figure 10-2)in effect, this is how the app domain bridges the unmanaged world and the managed world. However, it's important to note that all app domains in the same process share the same managed heap.

Figure 10-2. App domains provide their assemblies with access to the .NET runtime services


Sharing the same heap has security implications, which are addressed in Chapter 12.


10.1.2.1 App domains and threads

.NET managed threads have no app domain affinity, meaning that a thread can enter and exit any app domain that runs in the same underlying process. Typically, when you create a thread in your app domain, that thread executes a thread method and accesses only local objects. However, nothing prevents you from having threads created in one app domain access objects in another app domain in the same process. There is one detail you need to be aware of, though: when an app domain shuts down (i.e., when AppDomain.Unload( ) is called), it terminates all the threads that happen to be calling objects in it by calling THRead.Abort( ) on each of them.

10.1.3. App Domains and Remoting

Like traditional cross-process calls, you make cross-app domain calls using remoting, a programmatic act that accesses an object outside its hosting app domain. .NET uses exactly the same remote-call architecture for all cases, whether the cross-app domain call is between two app domains in the same process, between app domains in two different processes on the same machine, or between app domains on two separate machines (see Figure 10-3).

Figure 10-3. All cross-app domain calls use remoting


Accessing an object outside its context in the same app domain is a special case of remoting and is discussed in Chapter 11.


Clients in the same app domain as the called object can each have a direct reference to the object (see Figure 10-3). Clients in a different app domain use a proxy to connect to the object. A proxy is an object that provides exactly the same interfaces, public methods, properties, and members as the real object. .NET generates the proxy on the fly, based on the object's metadata. Even though the proxy has the same public entry points as the object, it can't serve the clients because the object's actual code and state reside only where the object is. All the proxy knows is how to bind to the object and forward the calls made on the proxy to the object. Forwarding a call to an object is called marshaling. Marshaling is a nontrivial feat: its end goal is to provide the client with the illusion that it's calling a local object and to provide the server with the illusion that it's servicing a local client. Neither the server nor the client explicitly uses remote mechanisms such as pipes, RPC, or sockets, because these details are encapsulated in the proxy. .NET does require, however, that if an object is accessed by proxy, the object's class must derive directly or indirectly from the abstract class MarshalByRefObject. You will learn more about marshaling and how it relates to the .NET remoting architecture later in this chapter.

10.1.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); 

10.1.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( )); 

As mentioned in Chapter 2, 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.


10.1.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 10-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 10-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.exethe 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 assemblyl, 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. For example, Chapter 12 uses the current AppDomain object to set an authorization policy to take advantage of .NET role-based security, and Appendix B uses the AppDomain object to change the default security principal.


10.1.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 10-2 demonstrates how to create a new app domain and then instantiate a new object in the new app domain.

Example 10-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 10-2 is similar to Example 10-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 10-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.

10.1.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 10-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.

10.1.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. The available hosting options are discussed later in this chapter.

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 (discussed later in this chapter), 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 10-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 10-4 depicts what the client and the host require of the server class library assembly at compile time and at runtime.

Figure 10-4. Client and host requirements for the server class library assembly


Making a class library DLL available to remote clients by hosting it in an EXE host is analogous to providing a surrogate process to a COM in-process DLL. In fact, the canonical COM surrogate process is called dllhost.exe.




Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net