Marshaling

Earlier, I stated that .NET lets multiple applications run within a single process under different application domains and that the runtime provides isolation between application domains. Let's see how this is done.

To provide isolation, the runtime ensures that an object from one application domain cannot directly invoke a method on another object that belongs to a different AppDomain. For the same reason, an object from one AppDomain cannot be directly passed to another AppDomain. This holds true irrespective of whether the domains reside in the same process, two different processes on the same machine, or processes on two different machines. From the runtime's perspective, an object that belongs to a domain that is different than that of the caller is deemed a remote object and is treated the same way no matter where the domain resides.

Methods on a remote object thus cannot be called directly, so how does one develop a distributed application if one cannot communicate across processes and across machines? Is there some indirect way to communicate with the remote object?

.NET remoting has been designed so that you don't really need to do anything special in your code to deal with remote objects. Just call the method on the remote object as if you are calling a method on a local object, pass in the method parameters, and obtain the method result. The infrastructure hides the complexity of calling methods on remote objects and returning the results.

Behind the scenes, when a remote object is created or an object is passed to another domain as a method parameter, the runtime transforms the object into a block of memory suitable for transmitting to the other context. This process is referred to as marshaling. Subsequently, the importing domain can unmarshal the block of data; that is, decode it to obtain a new object that is a copy of the original object.

Strictly speaking, cross-domain marshaling automatically implies cross-context marshaling. However, if the contexts belong to the same AppDomain, and if the object exhibits certain characteristics, the runtime may eliminate marshaling altogether. Essentially, the object can be accessed directly from any context within the AppDomain. Such an object is called a context-agile object. Context-agile objects live in the default context of the AppDomain. The behavior of a context-agile object is illustrated in Figure 6.3.

Figure 6.3. Context-agile object.


Essentially, the role of an AppDomain is to form the boundary for the context agility of an object (and to house contexts).

For marshaling purposes, .NET classifies objects into three distinct groups: marshal-by-value, marshal-by-reference, and nonremotable. Let's examine each of them. For our discussion, we may loosely interchange the terms AppDomain and context, but the idea should be clear.

Marshal-by-Value Objects

An object that is marked with the [Serializable] custom attribute is referred to as a marshal-by-value (MBV) object. When such an object is passed as a parameter to a remote AppDomain, the runtime serializes the object and transports it to the destination AppDomain, where the data is then deserialized to create a duplicate copy of the original object. Recall from Chapter 5 that the serialization mechanism uses reflection to serialize the member fields and that it is possible to customize the default serialization mechanism by further implementing the ISerializable interface.

Once the copy is in the destination AppDomain, any call to the object within the destination AppDomain is automatically directed to the copy.

The behavior of MBV objects is illustrated in Figure 6.4.

Figure 6.4. Marshal-by-value object.


You should use MBV objects when it is desirable to move the entire state of object to the target AppDomain. This reduces time- and resource-consuming round trips across network, process, and AppDomain boundaries. Keep in mind, though, that the caller is working on the copy; any change made in the state of the copy is not reflected in the original object. Likewise, any change made to the original object is not reflected in the copy.

Note that MBV objects are duplicated only when they cross their home AppDomain. Within an AppDomain, MBV objects are context-agile. Also note that all the base datatypes under .NET are marked as [Serializable]; that is, these datatypes are always marshaled by value.

Marshal-by-Reference Objects

An object that is derived from the standard class MarshalByRefObject, either directly or indirectly, is referred to as a marshal-by-reference (MBR) object. When such an object is passed from the original AppDomain to a different AppDomain, the runtime transparently creates a proxy object in the destination AppDomain and returns to the caller a reference to the proxy. The proxy object represents the actual object; that is, it implements the same methods and properties as the actual object. However, each time the caller invokes a method on the proxy, the runtime intercepts the method call and performs the following operations:

  1. Marshal the parameters.

  2. Switch to the object's actual context or AppDomain and apply any context-specific policies (e.g., synchronization, transaction, etc.).

  3. Unmarshal the parameters.

  4. Execute the call.

  5. Marshal the return value.

  6. Switch back to the caller's context.

  7. Unmarshal the return value and make it available to the caller.

The behavior of MBR objects is illustrated in Figure 6.5.

Figure 6.5. Marshal-by-reference object.


Let's write a program to create a new application domain and to display information about the default application domain as well as the newly created application domain.

The following code excerpt defines a class Foo containing a method DisplayDomainInfo that displays the application domain information:

// Project AppDomain/RemotableObject

public class Foo : MarshalByRefObject  {
     public void DisplayDomainInfo() {
       String s = "Domain="
         + AppDomain.CurrentDomain.FriendlyName
         + " Thread ID="
         + AppDomain.GetCurrentThreadId()
         + " Context ID="
         + Thread.CurrentContext.ContextID;
       Console.WriteLine(s);     }
}

In this code, class Foo is derived from MarshalByRefObject, which implies that any instance of Foo is an MBR object. Method DisplayDomainInfo displays three fields: the friendly name of the current domain, the identifier of the thread executing the code, and the context identifier of the current thread.

The following is our main application logic. Here, two instances of Foo are created, one in the default domain and one in a domain called MyNewAppDomain:

// Project AppDomain/RemotableObject

class MyApp {
     public static void Main()
     {
       // Display default domain info
       Foo fDefault = new Foo();
       fDefault.DisplayDomainInfo();

       // Create a new appdomain. Name it MyNewAppDomain
       AppDomain ad = AppDomain.CreateDomain("MyNewAppDomain");

       // Load RemotableObject.exe assembly in the new appdomain
       // and create an instance of Foo
       Foo fNew = (Foo)
         ad.CreateInstanceAndUnwrap("RemotableObject", "Foo");
       fNew.DisplayDomainInfo();
     }
}

The method CreateInstanceAndUnwrap creates an instance of specified type Foo from the specified assembly RemotableObject.exe in the domain on which the method is called. However, what gets stored in the variable fNew is a proxy object. If you compile this code as RemotableObject.exe and execute it, you will witness that the call to DisplayDomainInfo on fNew takes place in the newly created application domain. This is how .NET provides isolation among application domains.

As an exercise, remove MarshalByRefObject as the parent class on class Foo and add the [Serializable] attribute instead. When you compile and execute the new code, both the calls to DisplayDomainInfo happen in the default AppDomain.

Wrapped Objects

You may be wondering what the AndUnwrap part of AppDomain.CreateInstanceAndUnwrap stands for. When an object is passed from one AppDomain to another, typically the metadata for the object's type gets loaded in the new AppDomain. However, the .NET Framework provides an optimization wherein you can pass a different object that wraps the original object such that loading of metadata is deferred until the object is unwrapped.

The wrapped object is represented by the class ObjectHandle. You can obtain the ObjectHandle by calling AppDomain.CreateInstance and you can call ObjectHandle.Unwrap to obtain the original object (or its proxy if called from a different AppDomain).


You may be wondering what the behavior would be if an object inherits from MarshalByRefObject and is marked with the [Serializable] attribute. In this case, inheriting from MarshalByRefObject takes precedence. A remote domain receives a proxy object.

It must be clear that when you make a call on the proxy of an MBR object, the runtime intercepts the call and executes the call in the MBR object's AppDomain. There is just one exception to this rule—any static method on the MBR class is always executed in the caller's AppDomain. This is because static methods are not associated with an object, but the class itself. The interception occurs only if an object is accessed.

Application Domains and Threads

When you execute RemotableObject.exe, notice that the thread identifier displayed for both application domains is the same because threads do not have any affinity to application domains (or contexts). Application domains and contexts are spatial objects, whereas threads are temporal objects. A thread can traverse multiple contexts and application domains. Multiple threads can execute over the same application domain.

When an application domain is being unloaded, all threads that are executing in the domain must be unwound out of the domain. For any thread traversing through the domain, the runtime throws an exception of type ThreadAbortException. For the last transition out of the domain for a given thread, this ThreadAbortException is turned into an AppDomainAbortException.


Context-Bound Objects

At this point, it should be reasonably clear that the creator of an MBR object gets either a raw reference (if the object is created in the creator's AppDomain) or a proxy (in a different AppDomain).

Within an AppDomain, the context agility of an object can create problems in certain cases. Let's say the object has certain context attributes. For proper operation, the object depends on the context (the runtime environment) in which it was created. If a raw reference of this object were shared with another context in the same AppDomain, for example by using a global variable to store the reference, the context that is used during method execution is that of the caller, not that of the object. This results in a completely unpredictable behavior. For example, if the object relied on the same transaction service or security service to be available during method execution, it might not get one. Worse yet, it may get the caller's settings (which may be completely different). Almost all configured services would malfunction if the call were processed in the wrong context.

If the caller, however, had a reference to the proxy object instead, the runtime could intercept any call on the proxy and run appropriate configuration services before invoking the call on the method.

To prevent the context agility of the object, the .NET remoting architecture provides a base class, ContextBoundObject (namespace System). Any object that is based on ContextBoundObject is context bound; that is, the object can never leave the context it is created in. Any other context, even within the same AppDomain, can only get a proxy to this object. This behavior is illustrated in Figure 6.6.

Figure 6.6. Context-bound object.


Any method call made on the proxy is intercepted by the runtime. The subsequent operations are similar to that of an MBR object, as discussed earlier. As a matter of fact, the ContextBoundObject class itself is derived from MarshalByRefObject.

You define your own context-bound class by inheriting from ContextBoundObject. In addition, you can specify one or more context attributes on the class. This is illustrated in the following code excerpt:

// Project Contexts/ContextAttributes

[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo : ContextBoundObject  {
     public void DisplayInfo() {
       Console.WriteLine("Foo Context ID: {0}",
         Thread.CurrentContext.ContextID);
     }
}

This code uses a context attribute, SynchronizationAttribute (namespace System.Runtime.Remoting.Contexts). This attribute is used to provide synchronized access to an object. The value SynchronizationAttribute.REQUIRED tells the runtime that an instance of Foo should be created in a context that participates in synchronization. Such a context guarantees that no two threads can enter the context concurrently. One thread has to exit the context before another thread can enter it. We will learn more about synchronization in Chapter 8.

When to Use Context-Bound Objects

When should you use ContextBoundObject instead of MarshalByRefObject? If you intend to specify any context attributes on the class, then you should derive your class from ContextBoundObject. As a ContextBoundObject inherits from MarshalByRefObject, you get all the features of MarshalByRefObject as well.

For all other remotable cases, you can use MarshalByRefObject or [Serializable] as appropriate.


Instantiating a context-bound class is no different than instantiating any other class. You can use the standard new keyword in C#, as highlighted here:

public static void Main() {
     Console.WriteLine("Default Context ID={0}",
       Thread.CurrentContext.ContextID);
     Foo f1 = new Foo();
     ...
}

Here is the partial output from the application:

Default Context ID=0
Foo Context ID=1

When this code is run, Main is invoked from the default context (represented by the context ID=0). When Main creates an instance of Foo, the runtime checks if the caller's context is compatible with the context requirements for Foo. As the caller's context is not set to participate in synchronization, the runtime creates a new context, places the object in the new context (represented by context ID=1), and returns a proxy to the caller.

At this point, it is worth mentioning that there is yet another way to create instances using .NET. The BCL defines a class, Activator (namespace System), to deal with object activation[1] issues. Using the static method Activator.CreateInstance, you can create a new object. The following two lines of code are equivalent in functionality:

[1] For now, you can assume instantiation and activation are the same. The difference will become clear when we discuss the concept of object pooling in a later chapter.

Foo f1 = new Foo();
Foo f2 = (Foo) Activator.CreateInstance(typeof(Foo));

An interesting overload of Activator.CreateInstance is the one that lets you define one or more context attributes during activation. The attributes can be built in an array and passed as a parameter to the method. The following code excerpt illustrates this:

Bar b1 = (Bar) Activator.CreateInstance(
     typeof(Bar),
     null,
     new object[] {new SynchronizationAttribute(
       SynchronizationAttribute.REQUIRES)});

The first parameter to Activator.CreateInstance is the type of the object to be created. The second parameter is used to pass arguments to the constructor of class Bar. Passing a value null, as we have done here, invokes the default constructor. The third parameter is used to specify the required context attributes. In our case, we pass just one attribute, SynchronizationAttribute.

Okay, we are done with context-bound objects. In a later chapter on enterprise services, we will learn more about an important derivation of ContextBoundObject, ServicedComponent. This class lets you use COM+ runtime services such as transaction and object pooling.

Nonremotable Objects

An object that is neither inherited from MarshalByRefObject nor marked with the [Serializable] attribute is a nonremotable object. These objects can never leave their domain. Trying to pass such an object to a remote domain results in an exception of type SerializationException.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.179.119