Containing Assemblies Through the Use of Appdomains

for RuBoard

The key to achieving these goals in the .NET Framework runtime is the use of application domains (usually referred to as appdomains). These are represented by the System.AppDomain managed class. Each appdomain is itself a container for zero or more assemblies and, while assemblies within the same appdomain can interact freely through method calls as we've come to expect, interaction between assemblies in different appdomains is more tightly controlled. This provides the isolation needed between assemblies or groups of assemblies. Figure 10.2 illustrates the relationship between appdomains and assemblies.

Figure 10.2. Containing assemblies within appdomains.

graphics/10fig02.gif

Additionally, evidence can be associated with an appdomain in much the same way as it can with an assembly (allowing the appdomain to be granted a level of trust of its own). We'll look at this and why it's important later in this chapter.

Appdomains are actually present in every .NET Framework process ”a default appdomain is created before the first assembly is loaded. This is transparent to most managed applications; it's only when additional appdomains are needed that the explicit appdomain control is invoked. The default appdomain is fairly neutral; it has no special properties and, from a pure security point of view, is transparent (there is no evidence associated with it; therefore, the default appdomain is not granted any permissions and is not considered significant during security stack walks).

The host application need not run as managed code itself. It is perfectly possible to write an unmanaged application that starts up the .NET Framework explicitly and then interacts with managed components through the interop capabilities of the runtime (that is, treating managed objects as classic COM servers).

Explicit control of the runtime in this manner is handled through the COM interface ICorRuntimeHost . A full treatment of this interface (and related interfaces for controlling the garbage collector, runtime configuration, and so on) is beyond the scope of this book. To illustrate the basic concepts, the following example code starts up a runtime within the current process and explicitly creates an appdomain with a non-default application base (a file path used as a hint to find assemblies loaded within an appdomain). All error handling has been omitted for clarity.

 ICorRuntimeHost *pCorHost; IUnknown        *pSetup; IAppDomainSetup *pDomainSetup; IUnknown        *pAppDomain; // Create a hosting environment. CoCreateInstance(CLSID_CorRuntimeHost,                  NULL,                  CLSCTX_INPROC_SERVER,                  IID_ICorRuntimeHost,                  (void**)&pCorHost); // Startup the runtime. pCorHost->Start(); // Create an AppDomain Setup so we can set the AppBase. pCorHost->CreateDomainSetup(&pSetup); // QI for the IAppDomainSetup interface. pSetup->QueryInterface(__uuidof(IAppDomainSetup),                        (void**)&pDomainSetup); // Create a BSTR (wide character string prefixed with a DWORD // character count). BSTR bstrDirectory = SysAllocString(L"c:\MyDirectory"); // Set the AppBase. pDomainSetup->put_ApplicationBase(bstrDirectory); // Create a new AppDomain. pCorHost->CreateDomainEx(L"My Domain",                          pSetup,                          NULL,                          &pAppDomain); 

While it is possible to code the entirety of a host in this manner (and indeed it is often necessary to code at least the initialization section of a host this way, so that runtime configuration options can be applied before the runtime first starts up), it is usually more convenient to code the majority of the host in managed code.

This is primarily due to the fact that it is easier to manipulate managed classes from the managed world itself; there are no complex marshaling or context issues to resolve.

Managed hosts use AppDomain.CreateDomain to create isolated appdomains into which to load groups of assemblies. The .NET Framework remoting services are used to communicate between appdomains, which for the most part makes the process transparent (as though objects from different appdomains were making simple method calls on one another).

By default, objects are transported across appdomain boundaries by making a copy in the target appdomain (this is done using the serialization services of the .NET Framework). This provides some level of isolation already; code in one appdomain cannot directly manipulate the state of an object in another. This technique is known as marshal by value. A method call from an object in appdomain A to an object in appdomain B will typically proceed as follows :

  1. All the arguments to the method call are cloned in appdomain B (serialized in A and then deserialized in B).

  2. The method is called in appdomain B, passing it the copied arguments. A result is computed.

  3. The result is copied back into appdomain A (the result in B is no longer referenced and will be released by the garbage collector in due course).

So how do we make a call across an appdomain boundary in the first place? The key to this is that some object types are not marshal by value. Any class that derives from System.MarshalByRefObject will not be copied across appdomain boundaries. Instead, such objects are represented in remote appdomains by a transparent proxy object that points back to the real object in its original domain. These proxies are referred to as transparent because, from the point of view of the programmer, they look just like the real object (they have the same type and support all the same method calls).

The object we called a method on in this example was marshal-by-reference ; we know this because an appdomain transition occurred when the method call was made (a method call on a marshal by value object returned from another appdomain will simply result in the method being invoked on the local copy).

Very importantly, the AppDomain class itself is marshal-by-reference. So when a new appdomain is created via AppDomain.CreateDomain , the appdomain object returned is in fact a proxy to the real appdomain. Method calls on this proxy will therefore take place in the newly created domain, allowing it to be controlled by the host.

A point to note here is that System.Reflection.Assembly is not marshal by reference. If you use AppDomain.Load to load an assembly into a new appdomain, the assembly will also be loaded into your (calling) appdomain. That is because AppDomain.Load also returns the assembly object created (and because Assembly is marshal by value, this will cause a copy back into the calling domain). AppDomain.Load is really intended only for the use of unmanaged hosts that need the assembly object returned, but aren't calling from a managed appdomain in the first place, so don't have the copy back problem.

The best way for a hosting application to control each appdomain is to load a small controlling assembly into each one. This assembly will contain a marshal-by-reference class whose methods can be used to communicate between the initial (controlling) appdomain and the hosted appdomains. The host can then instruct the control class to load hosted assemblies directly into the correct appdomain.

The following is a sample control class:

 using System; using System.Reflection; public class HostControl : MarshalByRefObject {     // This method is called from the initial appdomain.     public void Load(String assembly)     {         // This load will occur in the target appdomain.         Assembly.Load(assembly);     } } 

The server can then start up a new appdomain, load the control class into it, and then load a hosted assembly through that control class in the following manner:

 using System; class Host {     public static void Main(String[] args)     {         // Create a new appdomain.         AppDomain domain = AppDomain.CreateDomain("NewDomain");         // Create an instance of a marshal-by-reference type (our         // control class) in the new appdomain.         HostControl control;         control = (HostControl)             domain.CreateInstanceAndUnwrap("HostControlAssembly",                                            "HostControl");         // Instruct the control class to load an assembly in the new         // appdomain.         control.Load("MyHostedAssembly");     } } 

When a host has no further need of the assemblies in an appdomain, it can unload that appdomain, which, in turn , unloads all the assemblies contained within (this will throw ThreadAbortException or AppDomainUnloaded exceptions on any threads executing in that appdomain). The initial (default) appdomain cannot be unloaded in this fashion.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

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