If the referenced object is in the same app domain as
the client, usually no proxies are involved, and the client will hold a
direct reference to the object. The question is, what happens when you
try to call methods on a remote object in another app domain? By
default, objects are not accessible from outside their own app domains,
even if the call is made from another app domain in the same process.
The rationale behind this decision is that .NET must first enforce app
domain isolation and security. If you intend your objects to be accessed
from outside their app domains, you must allow this explicitly in your
design and class definition. .NET provides two options for accessing an
object across an app domain boundary: by value or by reference.
Accessing an object by value means that when a client in App Domain 2
calls a method on an object in App Domain 1, the object is first copied
to App Domain 2, so the client gets its own cloned copy of the object.
Once a copy is transferred to the remote client, the two objects are
distinct and can change state independently. Any change made to the
object's state in App Domain 2 applies only to that local copy, which is
completely separate from the original object. This is often referred to
as marshaling by value and is similar to the analogous behavior in COM. The second way to access a remote object is by reference.
In this case, the remote clients hold only a reference to the object,
in the form of a proxy. Access by reference is often referred to as marshaling
by
reference (which was the standard behavior provided by COM).
1. Marshaling by Value
When an object is marshaled
by value, .NET must make a copy of the object's state, transfer the
state to the calling app domain, and build up a new object based on that
state. There are some difficulties, however. .NET needs to know which
parts of the object's state can be marshaled by value and which parts
can't. .NET has to obtain the state of an existing object and then build
a new object based on that state. And what if the object also wants to
provide some custom marshaling-by-value mechanism? Luckily, .NET already
has the infrastructure to handle such issues: serialization.
The requirements for marshaling by value and for generic serialization
are identical. To marshal an object by value, all .NET has to do is
serialize the object to a stream and deserialize the object in the
remote app domain. As a result, to enable marshaling by value, the
component must be serializable. Serializable components can either use the Serializable attribute or implement the ISerializable interface for custom serialization. For example, consider the following class definition:
[Serializable]
public class MyClass
{
public int Number;
}
Suppose an instance of this class has 3 as the value of the Number
member and is accessed across an app domain boundary by a remote
client. .NET marshals the object by value and gives the remote client a
copy of the object. Immediately after marshaling, the cloned object has 3 as the value of the Number member (see Figure 1). When the remote client in App Domain 3 assigns the value 4 to Number,
this assignment affects only its own new and distinct copy. Marshaling
by value works across any app domain boundary, be it in the same process
or across machines.
The primary use for
marshaling by value is when you want to pass a structure as a method
parameter. Typically, structures are used as data containers and have no
logic associated with them. Structures are very useful as method
parameters, but unless a struct is serializable, you can't use it as a
parameter to a remote call.
When you marshal a struct
by value to a remote object, you actually get the same semantics as
with a local object because value types are, by default, passed in by
value:
[Serializable]
public struct MyPoint
{
public int X;
public int Y;
}
public class RemoteClass : MarshalByRefObject
{
public void MyMethod(MyPoint point)
{
point.X++;
}
}
Changes made to the structure on the server side don't affect the structure on the client side:
//Remote client:
MyPoint point;
point.X = 1;
RemoteClass obj = new RemoteClass( );
obj.MyMethod(point);
Debug.Assert(point.X == 1);
However, if you pass the structure by reference using the out or ref parameter modifiers, changes made on the remote server side will affect the client's copy of the structure:
public class RemoteClass : MarshalByRefObject
{
public void MyMethod(ref MyPoint point)
{
point.X++;
}
}
//Remote client:
MyPoint point;
point.X = 1;
RemoteClass obj = new RemoteClass( );
obj.MyMethod(ref point);
Debug.Assert(point.X == 2);
This is the same as when you pass a structure by reference in the local case.
The usefulness of
marshaling a class instance by value is marginal, because the classic
client/server model doesn't fit well with marshaling by value.
Marshaling by value for reference types is provided for when the client
needs to make frequent calls of short duration to the object, and paying
the penalty for marshaling the object state to the client is more
efficient than paying the penalty multiple times for marshaling the call
to the object and back. Imagine, for example, a distributed
image-capturing and processing system. You want the machine capturing
the images to do so as fast as it can, and you want the processing to be
done on a separate machine. The capturing machine can create an image
object, then have the processing client access the object (that would
make it copy the image) and process it locally. That said, there is
usually a better design solution, such as transferring the image data
explicitly as a method parameter. In general, it's often a lot easier to
simply marshal a reference to the object to the client and have the
client invoke calls on the object via a proxy, as described next.
2. Marshaling by Reference
The second remoting
option is marshal by reference, and it's by far the more common way to
access objects across app domains. As explained previously, when
marshaling by reference is employed, the client accesses the remote object using a proxy .
The proxy forwards calls made on it to the actual object. To designate a
component for marshaling by reference, the class must derive directly
(or have one of its base classes derive) from the class MarshalByRefObject, defined in the System namespace. Objects derived from MarshalByRefObject are bound for life to the app domain in which they were created and can never leave it.
The client has a reference to a proxy, which forwards the call to TraceAppDomain( )
to the new app domain; this is why it traces the remote app domain's
name. If the remote object is serializable in addition to being derived
from MarshalByRefObject, you can use
serialization to persist the object's state, but the object is still
accessed by reference. Any static method or member variable on a
marshaled-by-reference class is always accessed directly; no proxy is
involved, because statics aren't associated with any particular object.
.NET does allow the client app domain
and the host app domain to be the same app domain. In that case, the
client can still interact with the marshaled-by-reference object using a
proxy, even though both share the same app domain. Clients may want to
do that in order to activate the object in different ways. However,
short-circuiting remoting this way is an esoteric case. In the vast
majority of cases, deriving from MarshalByRefObject has no bearing on intra-app domain calls, and clients in the same app domains get direct references to the object. |