Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Accessing Objects Across AppDomain Boundaries

Code in one AppDomain can communicate with types and objects contained in another AppDomain. However, access to these types and objects is allowed only through well-defined mechanisms. The following Ch22-1-AppDomains sample application demonstrates how to create a new AppDomain, load an assembly into it, and construct an instance of a type defined in that assembly. The code shows the different behaviors when constructing a type that is marshaled by reference, a type that

is marshaled by value, and a type that can’t be marshaled at all. The code also shows how these dif- ferently marshaled objects behave when the AppDomain that created them is unloaded. The Ch22-

1- AppDomains sample application has very little code in it, but I have added a lot of comments. After the code listing, I’ll walk through the code, explaining what the CLR is doing.

 

private static void Marshalling() {

// Get a reference to the AppDomain that the calling thread is executing in AppDomain adCallingThreadDomain = Thread.GetDomain();

 

// Every AppDomain is assigned a friendly string name (helpful for debugging)

// Get this AppDomain's friendly string name and display it String callingDomainName = adCallingThreadDomain.FriendlyName;

Console.WriteLine("Default AppDomain's friendly name={0}", callingDomainName);

 

// Get and display the assembly in our AppDomain that contains the 'Main' method String exeAssembly = Assembly.GetEntryAssembly().FullName; Console.WriteLine("Main assembly={0}", exeAssembly);

 

// Define a local variable that can refer to an AppDomain AppDomain ad2 = null;

 

// *** DEMO 1: Cross­AppDomain Communication Using Marshal­by­Reference *** Console.WriteLine("{0}Demo #1", Environment.NewLine);

 

// Create new AppDomain (security and configuration match current AppDomain) ad2 = AppDomain.CreateDomain("AD #2", null, null);

MarshalByRefType mbrt = null;

 

// Load our assembly into the new AppDomain, construct an object, marshal

// it back to our AD (we really get a reference to a proxy) mbrt = (MarshalByRefType)

ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType"); Console.WriteLine("Type={0}", mbrt.GetType()); // The CLR lies about the type

// Prove that we got a reference to a proxy object

Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));

 

// This looks like we're calling a method on MarshalByRefType but we're not.

// We're calling a method on the proxy type. The proxy transitions the thread

// to the AppDomain owning the object and calls this method on the real object. mbrt.SomeMethod();


// Unload the new AppDomain AppDomain.Unload(ad2);

// mbrt refers to a valid proxy object; the proxy object refers to an invalid AppDomain

 

try {

// We're calling a method on the proxy type. The AD is invalid, exception is thrown mbrt.SomeMethod();

Console.WriteLine("Successful call.");



}

catch (AppDomainUnloadedException) { Console.WriteLine("Failed call.");

}

 

// *** DEMO 2: Cross­AppDomain Communication Using Marshal­by­Value *** Console.WriteLine("{0}Demo #2", Environment.NewLine);

 

// Create new AppDomain (security and configuration match current AppDomain) ad2 = AppDomain.CreateDomain("AD #2", null, null);

 

// Load our assembly into the new AppDomain, construct an object, marshal

// it back to our AD (we really get a reference to a proxy) mbrt = (MarshalByRefType)

ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

 

// The object's method returns a COPY of the returned object;

// the object is marshaled by value (not by reference). MarshalByValType mbvt = mbrt.MethodWithReturn();

 

// Prove that we did NOT get a reference to a proxy object Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));

 

// This looks like we're calling a method on MarshalByValType and we are. Console.WriteLine("Returned object created " + mbvt.ToString());

 

// Unload the new AppDomain AppDomain.Unload(ad2);

// mbvt refers to valid object; unloading the AppDomain has no impact.

 

try {

// We're calling a method on an object; no exception is thrown Console.WriteLine("Returned object created " + mbvt.ToString()); Console.WriteLine("Successful call.");

}

catch (AppDomainUnloadedException) { Console.WriteLine("Failed call.");

}

 

// DEMO 3: Cross­AppDomain Communication Using non­marshalable type *** Console.WriteLine("{0}Demo #3", Environment.NewLine);

 

// Create new AppDomain (security and configuration match current AppDomain) ad2 = AppDomain.CreateDomain("AD #2", null, null);


// Load our assembly into the new AppDomain, construct an object, marshal

// it back to our AD (we really get a reference to a proxy) mbrt = (MarshalByRefType)

ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

 

// The object's method returns a non­marshalable object; exception NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingDomainName);

// We won't get here...

}

 

 

// Instances can be marshaled­by­reference across AppDomain boundaries public sealed class MarshalByRefType : MarshalByRefObject {

public MarshalByRefType() { Console.WriteLine("{0} ctor running in {1}",

this.GetType().ToString(), Thread.GetDomain().FriendlyName);

}

 

public void SomeMethod() {

Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);

}

 

public MarshalByValType MethodWithReturn() { Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName); MarshalByValType t = new MarshalByValType();

return t;

}

 

public NonMarshalableType MethodArgAndReturn(String callingDomainName) {

// NOTE: callingDomainName is [Serializable] Console.WriteLine("Calling from '{0}' to '{1}'.",

callingDomainName, Thread.GetDomain().FriendlyName); NonMarshalableType t = new NonMarshalableType();

return t;

}

}

 

 

// Instances can be marshaled­by­value across AppDomain boundaries [Serializable]

public sealed class MarshalByValType : Object {

private DateTime m_creationTime = DateTime.Now; // NOTE: DateTime is [Serializable]

 

public MarshalByValType() {

Console.WriteLine("{0} ctor running in {1}, Created on {2:D}", this.GetType().ToString(),

Thread.GetDomain().FriendlyName, m_creationTime);

}

 

public override String ToString() {

return m_creationTime.ToLongDateString();

}

}


// Instances cannot be marshaled across AppDomain boundaries

// [Serializable]

public sealed class NonMarshalableType : Object { public NonMarshalableType() {

Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);

}

}

 

If you build and run the Ch22-1-AppDomains application, you get the following output.

 

Default AppDomain's friendly name= Ch22­1­AppDomains.exe

Main assembly=Ch22­1­AppDomains, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

 

Demo #1

MarshalByRefType ctor running in AD #2 Type=MarshalByRefType

Is proxy=True Executing in AD #2 Failed call.

 

Demo #2

MarshalByRefType ctor running in AD #2 Executing in AD #2

MarshalByValType ctor running in AD #2, Created on Friday, August 07, 2009 Is proxy=False

Returned object created Saturday, June 23, 2012 Returned object created Saturday, June 23, 2012 Successful call.

 

Demo #3

MarshalByRefType ctor running in AD #2

Calling from 'Ch22­1­AppDomains.exe' to 'AD #2'. Executing in AD #2

 

Unhandled Exception: System.Runtime.Serialization.SerializationException: Type 'NonMarshalableType' in assembly 'Ch22­1­AppDomains, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

at MarshalByRefType.MethodArgAndReturn(String callingDomainName) at Program.Marshalling()

at Program.Main()

 

Now, I will discuss what this code and the CLR are doing.

 

Inside the Marshalling method, I first get a reference to an AppDomain object that identifies the AppDomain the calling thread is currently executing in. In Windows, a thread is always created in the context of one process, and the thread lives its entire lifetime in that process. However, a one-to-one correspondence doesn’t exist between threads and AppDomains. AppDomains are a CLR feature; Windows knows nothing about AppDomains. Because multiple AppDomains can be in a single Windows process, a thread can execute code in one AppDomain and then execute code in another AppDomain. From the CLR’s perspective, a thread is executing code in one AppDomain at a time. A thread can ask the CLR what AppDomain it is currently executing in by calling System.Threading. Thread’s static GetDomain method. The thread could also query System.AppDomain’s static, read- only CurrentDomain property to get the same information.


When an AppDomain is created, it can be assigned a friendly name. A friendly name is just a String that you can use to identify an AppDomain. This is typically useful in debugging scenarios. Because the CLR creates the default AppDomain before any of our code can run, the CLR uses the executable file’s file name as the default AppDomain’s friendly name. My Marshalling method queries the default AppDomain’s friendly name by using System.AppDomain’s read-only FriendlyName property.

Next, my Marshalling method queries the strong-name identity of the assembly (loaded into the default AppDomain) that defines the entry point method Main that calls Marshalling. This assembly defines several types: Program, MarshalByRefType, MarshalBy ValType, and NonMarshalable­ Type. At this point, we’re ready to look at the three demos that are all pretty similar to each other.

 

Demo #1: Cross-AppDomain Communication Using Marshal-by-Reference

In Demo #1, System.AppDomain’s static CreateDomain method is called, instructing the CLR to create a new AppDomain in the same Windows process. The AppDomain type actually offers several overloads of the CreateDomain method; I encourage you to study them and select the version that is most appropriate when you are writing code to create a new AppDomain. The version of Create­ Domain that I call accepts three arguments:

A String identifying the friendly name I want assigned to the new AppDomainI’m

passing in “AD #2” here.

A System.Security.Policy.Evidence identifying the evidence that the CLR should use to calculate the AppDomain’s permission setI’m passing null here so that the new AppDomain will inherit the same permission set as the AppDomain creating it. Usually, if you want to create a security boundary around code in an AppDomain, you’d construct a System. Security.PermissionSet object, add the desired permission objects to it (instances of types that implement the IPermission interface), and then pass the resulting Permission­ Set object reference to the overloaded version of the CreateDomain method that accepts a PermissionSet.

A System.AppDomainSetup identifying the configuration settings the CLR should use for the new AppDomainAgain, I’m passing null here so that the new AppDomain will inherit the same configuration settings as the AppDomain creating it. If you want the App- Domain to have a special configuration, construct an AppDomainSetup object, set its various properties to whatever you want, such as the name of the configuration file, and then pass the resulting AppDomainSetup object reference to the CreateDomain method.

 

Internally, the CreateDomain method creates a new AppDomain in the process. This AppDomain will be assigned the specified friendly name, security, and configuration settings. The new AppDomain will have its very own loader heap, which will be empty because there are currently no assemblies loading into the new AppDomain. When you create an AppDomain, the CLR does not create any threads in this AppDomain; no code runs in the AppDomain unless you explicitly have a thread call code in the AppDomain.


Now to create an instance of an object in the new AppDomain, we must first load an assembly into the new AppDomain and then construct an instance of a type defined in this assembly. This is precisely what the call to AppDomain’s public, instance CreateInstanceAndUnwrap method does. When call- ing CreateInstanceAndUnwrap, I pass two arguments: a String identifying the assembly I want loaded into the new AppDomain (referenced by the ad2 variable) and another String identifying the name of the type that I want to construct an instance of. Internally, CreateInstanceAndUnwrap causes the calling thread to transition from the current AppDomain into the new AppDomain. Now, the thread (which is inside the call to CreateInstanceAndUnwrap) loads the specified assembly

into the new AppDomain and then scans the assembly’s type definition metadata table, looking for the specified type (“MarshalByRefType”). After the type is found, the thread calls the MarshalBy­ RefType’s parameterless constructor. Now the thread transitions back to the default AppDomain so that CreateInstanceAndUnwrap can return a reference to the new MarshalByRefType object.

       
   
 
 

 

Although this sounds all fine and good, there is a problem: the CLR cannot allow a variable (root) living in one AppDomain to reference an object created in another AppDomain. If CreateInstance­ AndUnwrap simply returned the reference to the object, isolation would be broken, and isolation is the whole purpose of AppDomains! So, just before CreateInstanceAndUnwrap returns the object reference, it performs some additional logic.

You’ll notice that the MarshalByRefType type is derived from a very special base class: System.

MarshalByRefObject. When CreateInstanceAndUnwrap sees that it is marshalling an object whose type is derived from MarshalByRefObject, the CLR will marshal the object by reference across the AppDomain boundaries. Here is what it means to marshal an object by reference from one AppDomain (the source AppDomain where the object is really created) to another AppDomain (the destination AppDomain from where CreateInstanceAndUnwrap is called).

When a source AppDomain wants to send or return the reference of an object to a destination AppDomain, the CLR defines a proxy type in the destination AppDomain’s loader heap. This proxy type is defined using the original type’s metadata, and therefore, it looks exactly like the original type; it has all of the same instance members (properties, events, and methods). The instance fields are not part of the type, but I’ll talk more about this in a moment. This new type does have some instance fields defined inside of it, but these fields are not identical to that of the original data type. Instead, these fields indicate which AppDomain “owns” the real object and how to find the real object in the owning AppDomain. (Internally, the proxy object uses a GCHandle instance that refers to the real object. The GCHandle type is discussed in Chapter 21, “The Managed Heap and Garbage Collection.”)

After this type is defined in the destination AppDomain, CreateInstanceAndUnwrap creates an instance of this proxy type, initializes its fields to identify the source AppDomain and the real object, and returns a reference to this proxy object to the destination AppDomain. In my Ch22-1-AppDomains application, the mbrt variable will be set to refer to this proxy. Notice that the object returned from


CreateInstanceAndUnwrap is actually not an instance of the MarshalByRefType type. The CLR will usually not allow you to cast an object of one type to an incompatible type. However, in this situation, the CLR does allow the cast, because this new type has the same instance members as defined on the original type. In fact, if you use the proxy object to call GetType, it actually lies to you and says that it is a MarshalByRefType object.

However, it is possible to prove that the object returned from CreateInstanceAndUnwrap is actually a reference to a proxy object. To do this, my Ch22-1-AppDomains application calls System. Runtime.Remoting.RemotingService’s public, static IsTransparentProxy method passing in the reference returned from CreateInstanceAndUnwrap. As you can see from the output, Is­ TransparentProxy returns true, indicating that the object is a proxy.

Now, my Ch22-1-AppDomains application uses the proxy to call the SomeMethod method. Because the mbrt variable refers to a proxy object, the proxy’s implementation of this method is called. The proxy’s implementation uses the information fields inside the proxy object to transition the calling thread from the default AppDomain to the new AppDomain. Any actions now performed by this thread run under the new AppDomain’s security and configuration settings. Next, the thread uses the proxy object’s GCHandle field to find the real object in the new AppDomain, and then it uses the real object to call the real SomeMethod method.

There are two ways to prove that the calling thread has transitioned from the default AppDomain to the new AppDomain. First, inside the SomeMethod method, I call Thread.GetDomain().Friendly­ Name. This will return “AD #2” (as evidenced by the output) because the thread is now running in the new AppDomain created by using AppDomain.CreateDomain with “AD #2” as the friendly name parameter. Second, if you step through the code in a debugger and display the Call Stack window, the [AppDomain Transition] line marks where a thread has transitioned across an AppDomain boundary. See the Call Stack window near the bottom of Figure 22-2.

 
 

FIGURE 22-2The Debugger’s Call Stack window showing an AppDomain transition.


When the real SomeMethod method returns, it returns to the proxy’s SomeMethod method, which transitions the thread back to the default AppDomain, and then the thread continues executing code in the default AppDomain.

       
   
 
 

 

The next thing that my Ch22-1-AppDomains application does is call AppDomain’s public, static Unload method to force the CLR to unload the specified AppDomain, including all of the assemblies loaded into it. A garbage collection is forced to free up any objects that were created by code in the unloading AppDomain. At this point, the default AppDomain’s mbrt variable still refers to a valid proxy object; however, the proxy object no longer refers to a valid AppDomain (because it has been unloaded).

When the default AppDomain attempts to use the proxy object to call the SomeMethod method, the proxy’s implementation of this method is called. The proxy’s implementation determines that the AppDomain that contained the real object has been unloaded, and the proxy’s SomeMethod method throws an AppDomainUnloadedException to let the caller know that the operation cannot complete.

Wow! The CLR team at Microsoft had to do a lot of work to ensure AppDomain isolation, but it is important work because these features are used heavily and are being used more and more by

developers every day. Obviously, accessing objects across AppDomain boundaries by using marshal- by-reference semantics has some performance costs associated with it, so you typically want to keep the use of this feature to a minimum.

I promised you that I’d talk a little more about instance fields. A type derived from MarshalBy­ RefObject can define instance fields. However, these instance fields are not defined as being part of the proxy type and are not contained inside a proxy object. When you write code that reads from or writes to an instance field of a type derived from MarshalByRefObject, the JIT compiler emits code that uses the proxy object (to find the real AppDomain/object) by calling System.Object’s

FieldGetter or FieldSetter methods, respectively. These methods are private and undocumented;

they are basically methods that use reflection to get and set the value in a field. So although you can access fields of a type derived from MarshalByRefObject, the performance is particularly bad because the CLR really ends up calling methods to perform the field access. In fact, the performance is bad even if the object that you are accessing is in your own AppDomain.3

 

 
 

3 If the CLR required that all fields be private (which is recommended for good data encapsulation), then the FieldGetter and FieldSetter methods wouldn’t have to exist and accessing fields from methods could always have been direct, avoiding any performance penalty.


 

From a usability standpoint, a type derived from MarshalByRefObject should really avoid defining any static members. The reason is that static members are always accessed in the context of the calling AppDomain. No AppDomain transition can occur because a proxy object contains the information identifying which AppDomain to transition to, but there is no proxy object when calling

a static member. Having a type’s static members execute in one AppDomain while instance members execute in another AppDomain would make a very awkward programming model.

Because there are no roots in the second AppDomain, the original object referred to by the proxy could be garbage collected. Of course, this is not ideal. On the other hand, if the original object is held in memory indefinitely, then the proxy could go away and the original object would still live; this is also not ideal. The CLR solves this problem by using a lease manager. When a proxy for an object is created, the CLR keeps the object alive for five minutes. If no calls have been made through the proxy after five minutes, then the object is deactivated and will have its memory freed at the next garbage collection. After each call into the object, the lease manager renews the object’s lease so that it is guaranteed to remain in memory for another two minutes before being deactivated. If an application attempts to call into an object through a proxy after the object’s lease has expired, the CLR throws a System.Runtime.Remoting.RemotingException.

It is possible to override the default lease times of five minutes and two minutes by overriding MarshalByRefObject’s virtual InitializeLifetimeServices method. For more information, see the section titled “Lifetime Leases” in the .NET Framework SDK documentation.


Demo #2: Cross-AppDomain Communication Using Marshal-by-Value

Demo #2 is very similar to Demo #1. Again, another AppDomain is created exactly as Demo #1 did it. Then, CreateInstanceAndUnwrap is called to load the same assembly into the new AppDomain

and create an instance of a MarshalByRefType object in this new AppDomain. Next, the CLR creates a proxy to the object and the mbrt variable (in the default AppDomain) is initialized referring to the proxy. Now, using the proxy, I call MethodWithReturn. This method, which takes no arguments, will execute in the new AppDomain to create an instance of the MarshalByValType type before return- ing a reference to the object to the default AppDomain.

MarshalByValType is not derived from System.MarshalByRefObject, and therefore, the CLR cannot define a proxy type to create an instance from; the object can’t be marshaled by reference across the AppDomain boundary.

However, because MarshalByValType is marked with the [Serializable] custom attribute, MethodWithReturn is allowed to marshal the object by value. The next paragraph describes what it means to marshal an object by value from one AppDomain (the source AppDomain) to another AppDomain (the destination AppDomain). For more information about the CLR's serialization and deserialization mechanisms, see Chapter 24, "Runtime Serialization.”

When a source AppDomain wants to send or return a reference to an object to a destination App- Domain, the CLR serializes the object’s instance fields into a byte array. This byte array is copied from the source AppDomain to the destination AppDomain. Then, the CLR deserializes the byte array in the destination AppDomain. This forces the CLR to load the assembly that defines the type being deseri- alized into the destination AppDomain if it is not already loaded. Then, the CLR creates an instance of the type and uses the values in the byte array to initialize the object’s fields so that they have values identical to those they had in the original object. In other words, the CLR makes an exact duplicate of the source object in the destination’s AppDomain MethodWithReturn, and then returns a reference to this copy; the object has been marshaled by value across the AppDomain’s boundary.

       
   
 
 

 

At this point, the object in the source AppDomain and the object in the destination AppDomain live separate lifetimes, and their states can change independently of each other. If there are no roots in the source AppDomain keeping the original object alive (as in my Ch22-1-AppDomains application), its memory will be reclaimed at the next garbage collection.


To prove that the object returned from MethodWithReturn is not a reference to a proxy object, my Ch22-1-AppDomains application calls System.Runtime.Remoting.RemotingService’s public, static IsTransparentProxy method passing in the reference returned from MethodWithReturn. As you can see from the output, IsTransparentProxy returns false, indicating that the object is a real object, not a proxy.

Now, my program uses the real object to call the ToString method. Because the mbvt variable refers to a real object, the real implementation of this method is called, and no AppDomain transition occurs. This can be evidenced by examining the debugger’s Call Stack window, which will not show an [Appdomain Transition] line.

To further prove that no proxy is involved, my Ch22-1-AppDomains application unloads the new AppDomain and then attempts to call the ToString method again. Unlike in Demo #1, the call suc- ceeds this time because unloading the new AppDomain had no impact on objects “owned” by the default AppDomain, and this includes the object that was marshaled by value.

 

Demo #3: Cross-AppDomain Communication Using Non-Marshalable Types

Demo #3 starts out very similar to Demos #1 and #2. Just as in Demos #1 and #2, an AppDomain is created. Then, CreateInstanceAndUnwrap is called to load the same assembly into the new App- Domain, create a MarshalByRefType object in this new AppDomain, and have the mbrt variable refer to a proxy to this object.

Then, using this proxy, I call MethodArgAndReturn, which accepts an argument. Again, the CLR must maintain AppDomain isolation, so it cannot simply pass the reference to the argument into the new AppDomain. If the type of the object is derived from MarshalByRefObject, the CLR will make a proxy for it and marshal it by reference. If the object’s type is marked as [Serializable], the CLR will serialize the object (and its children) to a byte array, marshal the byte array into the new AppDomain, and then deserialize the byte array into an object graph, passing the root of the object graph into the MethodArgAndReturn method.

In this particular demo, I am passing a System.String object across AppDomain boundaries. The System.String type is not derived from MarshalByRefObject, so the CLR cannot create a proxy. Fortunately, System.String is marked as [Serializable], and therefore the CLR can marshal it by value, which allows the code to work. Note that for String objects, the CLR performs a special opti- mization. When marshaling a String object across an AppDomain boundary, the CLR just passes the reference to the String object across the boundary; it does not make a copy of the String object. The CLR can offer this optimization because String objects are immutable; therefore, it is impossible for code in one AppDomain to corrupt a String object’s characters.4 For more about String immu- tability, see Chapter 14, “Chars, Strings, and Working with Text.”

 

 
 

4 By the way, this is why the System.String class is sealed. If the class were not sealed, then you could define your own class derived from String and add your own fields. If you did this, there is no way that the CLR could ensure that your “string” class was immutable.


Inside MethodArgAndReturn, I display the string passed into it to show that the string came across the AppDomain boundary, and then I create an instance of the NonMarshalableType type and return a reference to this object to the default AppDomain. Because NonMarshalableType is not derived from System.MarshalByRefObject and is also not marked with the [Serializ­ able] custom attribute, MethodArgAndReturn is not allowed to marshal the object by reference or by value—the object cannot be marshaled across an AppDomain boundary at all! To report this,

MethodArgAndReturn throws a SerializationException in the default AppDomain. Because my program doesn’t catch this exception, the program just dies.

 

 


Date: 2016-03-03; view: 859


<== previous page | next page ==>
Nbsp;   CLR Hosting | Nbsp;   AppDomain Unloading
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.014 sec.)