Keys of AppDomain
The CLR scopes all objects, values, and object references to a particular AppDomain. An object resides in exactly one AppDomain, as do values. Moreover, object references must refer to objects in the same AppDomain. Like objects, types reside in exactly one AppDomain. If two AppDomains need to use a type, one must initialize and allocate the type once per AppDomain. Additionally, one must load and initialize the type’s module and assembly once for each AppDomain the type is used in.
Besides, unloading an AppDomain is the only way to unload a module or assembly. Unloading an AppDomain is also the only way to reclaim the memory consumed by a type’s static fields.
I also found some interesting things about the Thread class in .Net framework. System.Threading.Thread represents a schedulable entity in an AppDomain. It is soft thread; it is not recognized by the underlying OS. OS threads are referred to as hard threads. There is no one-to-one relationship between hard threads and CLR soft thread objects. A CLR soft thread object resides in exactly one AppDomain. In the current implementation of CLR, a given hard thread must have at most one soft thread object affiliated with it for a give AppDomain. And it maintains a per-AppDomain thread table to ensure that a given hard thread is affiliated with only one soft thread object per AppDomain.
We can use SetData and GetData to share information between the AppDomains. AppDomain.DoCallBack method allows you to specify a method on a type that will be executed in the foreign domain. This method must comply with the CrossAppDomainDelegate’s signature and must be static.
Table 1 lists several useful AppDomain events.
Table 1 AppDomain Events | ||
Event Name | EventArg Properties | Description |
AssemblyLoad | Assembly LoadedAssembly | Assembly has just been successfully loaded |
AssemblyResolve | string Name | Assembly reference cannot be resolved |
TypeResolve | string Name | Type reference cannot be resolved |
ResourceResolve | string Name | Resource reference cannot be resolved |
DomainUnload | None | Domain is about to be unloaded |
ProcessExit | None | Process is about to shut down |
UnhandledException | bool is Terminating, object ExceptionObject | Exception escaped thread-specific handlers |
Table 2 lists the properties of an AppDomain that are used by the assembly resolver.
Table 2 AppDomain Environment Properties | ||
AppDomainSetup Property | Get/SetData Name | Description |
ApplicationBase | APPBASE | Base directory for probing |
ApplicationName | APP_NAME | Symbolic name of application |
ConfigurationFile | APP_CONFIG_FILE | Name of.config file |
DynamicBase | DYNAMIC_BASE | Root of codegen directory |
PrivateBinPath | PRIVATE_BINPATH | Semicolon-delimited list of subdirs |
PrivateBinPathProbe | BINPATH_PROBE_ONLY | Suppress probing at APPBASE ("*" or null) |
ShadowCopyFiles | FORCE_CACHE_INSTALL | Enable/disable shadow copy (Boolean) |
ShadowCopyDirectories | SHADOW_COPY_DIRS | Directories to shadow-copy from |
CachePath | CACHE_BASE | Directory the shadow-copy to |
LoaderOptimization | LOADER_OPTIMIZATION | JIT-compile per-process or per-domain |
DiablePublisherPolicy | DISALLOW_APP | Suppress component-supplied version policy |
AppDomain Property | Description | |
BaseDirectory | Alias to AppDomainSetup.ApplicationBase | |
RelativeSearchPath | Alias to AppDomainSetup.PrivateBinPath | |
DynamicDirectory | Directory for dynamic assemblies ( | |
FriendlyName | Name of AppDomain used in debugger |
AppDomain can also affect the way the JIT compiler works. When the CLR initializes an AppDomain, the CLR accepts a loader optimization flag (System.LoaderOptimization) that controls hwo code is JIT-compiled for modules loaded by that AppDomain. As shown in Table 3, this flag has three possible values.
Table 3 LoaderOptimization Enumeration/Attribute | |||||
Value | Expected Domains in Process | Each Domain Expected to Run ... | Code for MSCORLIB | Code for Assemblies in GAC | Code for Assemblies not in GAC |
SingleDomain | One | N/A | Per-process | Per-domain | Per-domain |
MultiDomain | Many | Same Program | Per-process | Per-process | Per-process |
MultiDomainHost | Many | Different Programs | Per-process | Per-process | Per-domain |
Marshal and Unmarshal
As I have mentioned before, all objects, values and object references are scoped to a particular AppDomain. When one needs to pass a reference or value to another AppDomain, one must first marshal it. .Net provides RemotingServices.Marshal to do this job. It takes an object reference of type System.Object and returns a serializable System.Runtime.RemotingServices.ObjRef object that can be passed in serialized form. to other AppDomains. Upon receiving a serialized ObjRef, one can obtain a valid object reference using the RemotingServices.Unmarshal method. When calling AppDomain.SetData on a foreign AppDomain, the CLR calls RemotingServices.Marshal. Similarly, calling AppDomain.GetData on a foreign AppDomain returns a marshaled reference, which is converted via RemotingServices.Unmarshal just prior to the method's completion.
If a type derives from System.MarshalByRefObject, then it’s AppDomain-bound, which means the type will marshal by reference and the CLR will give the receiver of the marshaled object (reference) a project that remotes all member access back to the object’s home AppDomain. Note, the proxies never remote static methods. If types don’t derive from MarshalByRefObject but do support object serialization (System.Serializable), they are considered unbound to any AppDomain. They will marshal by value. And the CLR will give the receiver of the marshaled object (reference) a disconnected clone of the original object.
Marshaling typically happens implicitly when a call is made to a cross-AppDomain proxy. The CLR marshals the input parameters to the method call into a serialized request message that the CLR sends to the target AppDomain. When the target AppDomain receives the serialized request, it first deserializes the message and pushes the parameters onto a new stack frame. After the CLR dispatches the method to the target object, the CLR then marshals the output parameters and return value into a serialized response message that the CLR sends back to the caller's AppDomain where the CLR unmarshals them and places them back on the caller's stack.The following diagram show the architecture.
Figure 1. Cross-Domain Method Calls
When one use cross-AppDomain proxies, then both AppDomains must have access to the same assemblies. Moreover, when the two AppDomains reside on different machines, both machines must have access to the shared types’ metadata.
CreateInstance
Now, let’s see what happens when we use AppDomain.CreateInstance method to create remote object in another AppDomain. This method only returns an object handle, which is similar to marshaled object references. And this method returns object handle rather than a real object reference, this can avoid requiring metadata in the caller’s AppDomain, in other words, in the caller’s AppDomain, it does need to load the assembly. An attempt to call CreateInstance on a target application domain that is not the current application domain will result in a successful load of the assembly in the target application domain. Since an Assembly is not MarshalByRefObject, when this method attempts to return the Assembly for the loaded assembly to the current application domain, the common language runtime will try to load the assembly into the current application domain and the load might fail. Actually, AppDomain.CreateInstance method delegates the work to Activator.CreateInstance; Internally, Activator loads the assembly first, then it tries find the class’s public constructors; after that, it checks whether the type can be marshaled. Then, it continues to use RuntimeType (internal class) to create the object. Finally, it wraps this object in an ObjectHandle object, and returns this ObjectHandle object back to the parent AppDomain.
Unload
Another thing I am concerned is how to unload an AppDomain. AppDomain.Unload method is used to unload the whole AppDomain. In the .NET Framework version 2.0 there is a thread dedicated to unloading application domains. This improves reliability, especially when the .NET Framework is hosted. When a thread calls Unload, the target domain is marked for unloading. The dedicated thread attempts to unload the domain, and all threads in the domain are aborted. If a thread does not abort, for example because it is executing unmanaged code, or because it is executing a finally block, then after a period of time a CannotUnloadAppDomainException is thrown in the thread that originally called Unload. If the thread that could not be aborted eventually ends, the target domain is not unloaded. Thus, in the .NET Framework version 2.0 domain is not guaranteed to unload, because it might not be possible to terminate executing threads. I try to use Reflector to dig into the .net code. I find that it needs to get the AppDomain ID first. In this method, it uses RemotingServices.IsTransparentProxy to see whether the AppDomain is a transparent proxy or a real object. If it is, it gets the AppDomain ID from the proxy by invoking RemotingServices.GetServiceDomainIdForProxy method; else, it gets the ID directly from the AppDomain.Id. After that, it can use this ID to unload the AppDomain. But, I can’t find out what really happens internally.