Chapter 19. Marshaling and Remoting

The days of integrated programs all running in a single process on a single machine are, if not dead, at least seriously wounded. Today’s programs consist of complex components running in multiple processes, often across the network. The Web has facilitated distributed applications in a way that was unthinkable even a few years ago, and the trend is toward distribution of responsibility.

A second trend is toward centralizing business logic on large servers. Although these trends appear to be contradictory, in fact they are synergistic: business objects are being centralized while the user interface and even some middleware are being distributed.

The net effect is that objects need to be able to talk with one another at a distance. Objects running on a server handling the web user interface need to be able to interact with business objects living on centralized servers at corporate headquarters.

The process of moving an object across a boundary is called marshal by value . Boundaries exist at various levels of abstraction in your program. The most obvious boundary is between objects running on different machines.

The process of preparing an object to be remoted is called marshaling. On a single machine, objects might need to be marshaled across context, app domain, or process boundaries.

A process is essentially a running application. If an object in your word processor wants to interact with an object in your spreadsheet, they must communicate across process boundaries.

Processes are divided into application domains (often called app domains); these in turn are divided into various contexts . App domains act like lightweight processes, and contexts create boundaries that objects with similar rules can be contained within. At times, objects will be marshaled across both context and app domain boundaries, as well as across process and machine boundaries.

When an object is marshaled by value, it appears to be sent through the wire from one computer to another, much like Captain Kirk being teleported down to the surface of a planet some miles below the orbiting USS Enterprise.

In Star Trek, Kirk was actually sent to the planet, but in the .NET edition, it is all an illusion. If you are standing on the surface of the planet, you might think you are seeing and talking with the real Kirk, but you aren’t talking to Kirk at all: you are talking to a proxy, perhaps a hologram, whose job is to take your message up to the Enterprise where it is relayed to the real Kirk.

Between you and Kirk there are also a number of “sinks.” A sink is an object whose job is to enforce policy. For example, if Kirk tries to tell you something that might influence the development of your civilization, the prime-directive sink might disallow the transmission.

When the real Kirk responds, he passes his response through various sinks until it gets to the proxy and the proxy tells you. It seems to you as though Kirk is really there, but he’s actually sneaking up behind you to thwart your nefarious plans. Alas, it turns out that it was Mr. Sulu who was controlling the hologram the whole time. Better luck next episode.

The actual transmission of your message is done by a channel . The channel’s job is to know how to move the message from the Enterprise to the planet. The channel works with a formatter . The formatter makes sure the message is in the right format. Perhaps you speak only Vulcan, and the poor Captain doesn’t. The formatter can translate your message into Federation Standard, and translate Kirk’s response from Federation Standard back to Vulcan. You appear to be talking with one another, but the formatter (known as the universal translator in the Star Trek universe) is silently facilitating the communication.

This chapter demonstrates how your objects can be marshaled across various boundaries, and how proxies and stubs can create the illusion that your object has been squeezed through the network cable to a machine across the office or around the world. In addition, this chapter explains the role of formatters, channels, and sinks, and how to apply these concepts to your programming.

Application Domains

A process is, essentially, a running application. Each .NET application runs in its own process. If you have Word, Excel, and Visual Studio open, you have three processes running. If you open Outlook, another process starts up. Each process is subdivided into one or more application domains. An app domain acts like a process but uses fewer resources.

App domains can be independently started and halted. They are secure, lightweight, and versatile. An app domain can provide fault tolerance; if you start an object in a second app domain and it crashes, it will bring down the app domain but not your entire program. You can imagine that web servers might use app domains for running users’ code; if the code has a problem, the web server can maintain operations.

An app domain is encapsulated by an instance of the AppDomain class, which offers a number of methods and properties. A few of the most important are listed in Table 19-1.

Table 19-1. Methods and properties of the AppDomain class

Method or property

Details

CurrentDomain

Public static property that returns the application domain for the current thread

CreateDomain( )

Overloaded public static method that creates a new application domain

GetCurrentThreadID( )

Public static method that returns the current thread identifier

Unload( )

Public static method that removes the specified app domain

FriendlyName

Public property that returns the friendly name for this app domain

DefineDynamicAssembly( )

Overloaded public method that defines a dynamic assembly in the current app domain

ExecuteAssembly( )

Public method that executes the designated assembly

GetData( )

Public method that gets the value stored in the current application domain given a key

Load( )

Public method that loads an assembly into the current app domain

SetAppDomainPolicy( )

Public method that sets the security policy for the current app domain

SetData( )

Public method that puts data into the specified app domain property

App domains also support a variety of events—including AssemblyLoad , AssemblyResolve , ProcessExit , and ResourceResolve-- that are fired as assemblies are found, loaded, run, and unloaded.

Every process has an initial app domain, and can have additional app domains as you create them. Each app domain exists in exactly one process. Until now, all the programs in this book have been in a single app domain: the default app domain. Each process has its own default app domain. In many, perhaps in most of the programs you write, the default app domain will be all that you’ll need.

However, there are times when a single domain is insufficient. You might create a second app domain if you need to run a library written by another programmer. Perhaps you don’t trust the library, and want to isolate it in its own domain so that if a method in the library crashes the program, only the isolated domain will be affected. If you were the author of Internet Information Server (IIS), Microsoft’s web hosting software), you might spin up a new app domain for each plug-in application or each virtual directory you host. This would provide fault tolerance so that if one web application crashed, it would not bring down the web server.

It is also possible that the other library might require a different security environment; creating a second app domain allows the two security environments to coexist. Each app domain has its own security, and the app domain serves as a security boundary.

App domains aren’t threads and should be distinguished from threads. A Win32 thread exists in one app domain at a time, and a thread can access (and report) which app domain it is executing in. App domains are used to isolate applications; within an app domain there might be multiple threads operating at any given moment (see Chapter 20).

To see how app domains work, let’s set up an example. Suppose you wish your program to instantiate a Shape class, but in a second app domain.

Tip

There is no good reason for this Shape class to be put in a second app domain, except to illustrate how these techniques work. It is possible, however, that more complex objects might need a second app domain to provide a different security environment. Further, if you are creating classes that might engage in risky behavior, you might like the protection of starting them in a second app domain.

Normally, you’d load the Shape class from a separate assembly, but to keep this example simple, you’ll just put the definition of the Shape class into the same source file as all the other code in this example (see Chapter 17). Further, in a production environment, you might run the Shape class methods in a separate thread, but for simplicity, you’ll ignore threading for now. (Threading is covered in detail in Chapter 20.) By sidestepping these ancillary issues, you can keep the example straightforward and focus on the details of creating and using application domains and marshaling objects across app domain boundaries.

Creating and Using App Domains

Create a new app domain by calling the static method CreateDomain( ) on the AppDomain class:

AppDomain ad2 = 
   AppDomain.CreateDomain("Shape Domain");

This creates a new app domain with the friendly name Shape Domain. The friendly name is a convenience to the programmer; it is a way to interact with the domain programmatically without knowing the internal representation of the domain. You can check the friendly name of the domain you’re working in with the property System.AppDomain.CurrentDomain.FriendlyName.

Once you have instantiated an AppDomain object, you can create instances of classes, interfaces, and so forth, using its CreateInstance( ) method. Here’s the signature:

public ObjectHandle CreateInstance( 
   string assemblyName,
   string typeName,
   bool ignoreCase, 
   BindingFlags bindingAttr,
   Binder binder,
   object[] args,
   CultureInfo culture,
   object[] activationAttributes,
   Evidence securityAttributes
);

And here’s how to use it:

ObjectHandle oh = ad2.CreateInstance( 
"ProgCSharp",                                   // the assembly name
"ProgCSharp.Shape",                             // the type name with namespace
false,                                          // ignore case
System.Reflection.BindingFlags.CreateInstance,  // flag
null,                                           // binder
new object[] {3, 5},                            // args
null,                                           // culture
null,                                           // activation attributes
null );                                         // security attributes

The first parameter (ProgCSharp) is the name of the assembly, and the second (ProgCSharp.Shape) is the name of the class. The class name must be fully qualified by namespaces.

A binder is an object that enables dynamic binding of an assembly at runtime. Its job is to allow you to pass in information about the object you want to create, to create that object for you, and to bind your reference to that object. In the vast majority of cases, including this example, you’ll use the default binder, which is accomplished by passing in null.

It is possible, of course, to write your own binder that might, for example, check your ID against special permissions in a database and reroute the binding to a different object, based on your identity or your privileges.

Tip

Binding typically refers to attaching an object name to an object. Dynamic binding refers to the ability to make that attachment when the program is running, as opposed to when it is compiled. In this example, the Shape object is bound to the instance variable at runtime, through the app domain’s CreateInstance( ) method.

Binding flags help the binder fine-tune its behavior at binding time. In this example, use the BindingFlags enumeration value CreateInstance. The default binder normally looks at public classes only for binding, but you can add flags to have it look at private classes if you have the right permissions.

When you bind an assembly at runtime, don’t specify the assembly to load at compile time; rather, determine which assembly you want programmatically, and bind your variable to that assembly when the program is running.

The constructor you’re calling takes two integers, which must be put into an object array (new object[] {3, 5}). You can send null for the culture because you’ll use the default (en) culture and won’t specify activation attributes or security attributes.

You get back an object handle , which is a type that is used to pass an object (in a wrapped state) between multiple app domains without loading the metadata for the wrapped object in each object through which the ObjectHandle travels. You can get the actual object itself by calling Unwrap( ) on the object handle, and casting the resulting object to the actual type—in this case, Shape.

The CreateInstance() method provides an opportunity to create the object in a new app domain. If you were to create the object with new, it would be created in the current app domain.

Marshaling Across App Domain Boundaries

You’ve created a Shape object in the Shape domain, but you’re accessing it through a Shape object in the original domain. To access the shape object in another domain, you must marshal the object across the domain boundary.

Marshaling is the process of preparing an object to move across a boundary; once again, like Captain Kirk transporting to the planet’s surface. Marshaling is accomplished in two ways: by value or by reference. When an object is marshaled by value, a copy is made. It is as if I called you on the phone and asked you to send me your calculator, and you called up the office supply store and had them send me one that is identical to yours. I can use the copy just as I would the original, but entering numbers on my copy has no effect on your original.

Marshaling by reference is almost like sending me your own calculator. Here’s how it works. You don’t actually give me the original, but instead keep it in your house. You do send me a proxy. The proxy is very smart: when I press a button on my proxy calculator, it sends a signal to your original calculator, and the number appears over there. Pressing buttons on the proxy looks and feels to me just like I touched your original calculator.

Understanding marshaling with proxies

The Captain Kirk and calculator analogies are fine as far as analogies go, but what actually happens when you marshal by reference? The CLR provides your calling object with a transparent proxy (TP).

The job of the TP is to take everything known about your method call (the return value, the parameters, etc.) off of the stack and stuff it into an object that implements the IMessage interface. That IMessage is passed to a RealProxy object.

RealProxy is an abstract base class from which all proxies derive. You can implement your own real proxy, or any of the other objects in this process except for the transparent proxy. The default real proxy will hand the IMessage to a series of sink objects.

Any number of sinks can be used depending on the number of policies you wish to enforce, but the last sink in a chain will put the IMessage into a channel. Channels are split into client-side and server-side channels, and their job is to move the message across the boundary. Channels are responsible for understanding the transport protocol. The actual format of a message as it moves across the boundary is managed by a formatter. The .NET Framework provides two formatters: a SOAP formatter, which is the default for HTTP channels, and a Binary formatter, which is the default for TCP/IP channels. You are free to create your own formatters and, if you are truly a glutton for punishment, your own channels.

Once a message is passed across a boundary, it is received by the server-side channel and formatter, which reconstitute the IMessage and pass it to one or more sinks on the server side. The final sink in a sink chain is the StackBuilder , whose job is to take the IMessage and turn it back into a stack frame so that it appears to be a function call to the server.

Specifying the marshaling method

To illustrate the distinction between marshaling by value and marshaling by reference, in the next example you’ll tell the Shape object to marshal by reference but give it a member variable of type Point, which you’ll specify as a marshal by value.

Note that each time you create a class that might be used across a boundary, you must choose how it will be marshaled. Normally, objects can’t be marshaled at all; you must take action to indicate that an object can be marshaled, either by value or by reference.

The easiest way to make an object marshal by value is to mark it with the Serializable attribute:

[Serializable]
public class Point

When an object is serialized, its internal state is written out to a stream, either for marshaling or for storage. The details of serialization are covered in Chapter 21.

The easiest way to make an object marshal by reference is to derive its class from MarshalByRefObject :

public class Shape : MarshalByRefObject

The Shape class will have just one member variable, upperLeft. This variable will be a Point object, which holds the coordinates of the upper-left corner of the shape.

The constructor for Shape will initialize its Point member:

public Shape(int upperLeftX, int upperLeftY)
{
    Console.WriteLine( "[{0}] Event{1}",
       System.AppDomain.CurrentDomain.FriendlyName, 
       "Shape constructor");
    upperLeft = new Point(upperLeftX, upperLeftY);
}

Provide Shape with a method for displaying its position:

public void ShowUpperLeft()
{
    Console.WriteLine( "[{0}] Upper left: {1},{2}",
        System.AppDomain.CurrentDomain.FriendlyName, 
        upperLeft.X, upperLeft.Y);
}

Also provide a second method for returning its upperLeft member variable:

public Point GetUpperLeft()
{
    return upperLeft;
}

The Point class is very simple as well. It has a constructor that initializes its two member variables and accessors to get their value.

Once you create the Shape, ask it for its coordinates:

s1.ShowUpperLeft( );     // ask the object to display

Then ask it to return its upperLeft coordinate as a Point object that you’ll change:

Point localPoint = s1.GetUpperLeft();

localPoint.X = 500;
localPoint.Y = 600;

Ask that Point to print its coordinates, and then ask the Shape to print its coordinates. So, will the change to the local Point object be reflected in the Shape? That depends on how the Point object is marshaled. If it is marshaled by value, the localPoint object will be a copy, and the Shape object will be unaffected by changing the localPoint variables’ values. If, on the other hand, you change the Point object to marshal by reference, you’ll have a proxy to the actual upperLeft variable, and changing that will change the Shape. Example 19-1 illustrates this point. Make sure you build Example 19-1 in a project named ProgCSharp. When Main( ) instantiates the Shape object, the method is looking for ProgCSharp.exe.

Example 19-1. Marshaling across app domain boundaries

#region Using directives

using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Reflection;
using System.Text;

#endregion

namespace Marshaling
{

   // for marshal by reference comment out
   // the attribute and uncomment the base class
   [Serializable]
   public classPoint  // : MarshalByRefObject
   {
      private int x;
      private int y;

      public Point (int x, int y)
      {
         Console.WriteLine( "[{0}] {1}",
            System.AppDomain.CurrentDomain.FriendlyName, 
            "Point constructor");

         this.x = x;
         this.y = y;
      }
    
      public int X
      {
         get
         {
            Console.WriteLine( "[{0}] {1}",
               System.AppDomain.CurrentDomain.FriendlyName, 
               "Point x.get");

            return this.x;
         }

         set
         {
            Console.WriteLine( "[{0}] {1}",
               System.AppDomain.CurrentDomain.FriendlyName, 
               "Point x.set");
            this.x = value;
         }
      }

      public int Y
      {
         get
         {
            Console.WriteLine( "[{0}] {1}",
               System.AppDomain.CurrentDomain.FriendlyName, 
               "Point y.get");
            return this.y;
         }

         set
         {
            Console.WriteLine( "[{0}] {1}",
               System.AppDomain.CurrentDomain.FriendlyName, 
               "Point y.set");
            this.y = value;
         }
      }
   }

   // the shape class marshals by reference
   public class Shape : MarshalByRefObject
   {
      private Point upperLeft;

      public Shape(int upperLeftX, int upperLeftY)
      {
         Console.WriteLine( "[{0}] {1}",
            System.AppDomain.CurrentDomain.FriendlyName, 
            "Shape constructor");

         upperLeft = new Point(upperLeftX, upperLeftY);
      }
      public Point GetUpperLeft( )
      {
         return upperLeft;
      }

      public void ShowUpperLeft( )
      {
         Console.WriteLine( "[{0}] Upper left: {1},{2}",
            System.AppDomain.CurrentDomain.FriendlyName, 
            upperLeft.X, upperLeft.Y);
      }
   }
   public class Tester
   {
      public static void Main( )
      {

         Console.WriteLine( "[{0}] {1}",
            System.AppDomain.CurrentDomain.FriendlyName, 
            "Entered Main");
                           
         // create the new app domain
         AppDomain ad2 = 
            AppDomain.CreateDomain("Shape Domain");
    
         //  Assembly a = Assembly.LoadFrom("ProgCSharp.exe");
         //  Object theShape = a.CreateInstance("Shape");
         // instantiate a Shape object
         ObjectHandle oh = ad2.CreateInstance( 
            "Marshaling",
            "Marshaling.Shape", false,
            System.Reflection.BindingFlags.CreateInstance,
            null, new object[] {3, 5},
            null, null, null );

         Shape s1 = (Shape) oh.Unwrap( );

         s1.ShowUpperLeft( );     // ask the object to display

         // get a local copy? proxy?
         Point localPoint = s1.GetUpperLeft( );

         // assign new values
         localPoint.X = 500;
         localPoint.Y = 600;

         // display the value of the local Point object
         Console.WriteLine( "[{0}] localPoint: {1}, {2}",
            System.AppDomain.CurrentDomain.FriendlyName, 
            localPoint.X, localPoint.Y);

         s1.ShowUpperLeft( );     // show the value once more
      }
   }
}

Output:
[[Marshaling.vshost.exe] Entered Main
[Shape Domain] Shape constructor
[Shape Domain] Point constructor
[Shape Domain] Point x.get
[Shape Domain] Point y.get
[Shape Domain] Upper left: 3,5
[Marshaling.vshost.exe] Point x.set
[Marshaling.vshost.exe] Point y.set
[Marshaling.vshost.exe] Point x.get
[Marshaling.vshost.exe] Point y.get
[Marshaling.vshost.exe] localPoint: 500, 600
[Shape Domain] Point x.get
[Shape Domain] Point y.get
[Shape Domain] Upper left: 3,5

Read through the code, or better yet, put it in your debugger and step through it. The output reveals that the Shape and Point constructors run in the Shape domain, as does the access of the values of the Point object in the Shape.

The property is set in the original app domain, setting the local copy of the Point object to 500 and 600. Because Point is marshaled by value, however, you are setting a copy of the Point object. When you ask the Shape to display its upperLeft member variable, it is unchanged.

To complete the experiment, comment out the attribute at the top of the Point declaration and uncomment the base class:

// [serializable]
public class Point   : MarshalByRefObject

Now run the program again. The output is quite different:

[Marshaling.vshost.exe] Entered Main
[Shape Domain] Shape constructor
[Shape Domain] Point constructor
[Shape Domain] Point x.get
[Shape Domain] Point y.get
[Shape Domain] Upper left: 3,5
[Shape Domain] Point x.set
[Shape Domain] Point y.set
[Shape Domain] Point x.get
[Shape Domain] Point y.get
[Marshaling.vshost.exe] localPoint: 500, 600
[Shape Domain] Point x.get
[Shape Domain] Point y.get
[Shape Domain] Upper left: 500,600

This time you get a proxy for the Point object and the properties are set through the proxy on the original Point member variable. Thus, the changes are reflected within the Shape itself.

Context

App domains themselves are subdivided into contexts. Contexts can be thought of as boundaries within which objects share usage rules. These usage rules include synchronization transactions (see Chapter 20), and so forth.

Context-Bound and Context-Agile Objects

Objects are either context-bound or context-agile. If they are context-bound, they exist in a context, and to interact with them, the message must be marshaled. If they are context-agile, they act within the context of the calling object: their methods execute in the context of the object that invokes the method and so marshaling isn’t required.

Suppose you have an object A that interacts with the database and so is marked to support transactions. This creates a context. All method calls on A occur within the context of the protection afforded by the transaction. Object A can decide to roll back the transaction, and all actions taken since the last commit are undone.

Suppose that you have another object, B, which is context-agile. Now suppose that object A passes a database reference to object B and then calls methods on B. Perhaps A and B are in a callback relationship, in which B will do some work and then call A back with the results. Because B is context-agile, B’s method operates in the context of the calling object; thus it will be afforded the transaction protection of object A. The changes B makes to the database will be undone if A rolls back the transaction because B’s methods execute within the context of the caller. So far, so good.

Should B be context-agile or context-bound? In the case examined so far, B worked fine being agile. Suppose one more class exists: C. C doesn’t have transactions, and it calls a method on B that changes the database. Now A tries to roll back, but unfortunately, the work B did for C was in C’s context and thus was not afforded the support of transactions. Uh-oh: that work can’t be undone.

If B was marked context-bound when A created it, B would have inherited A’s context. In that case, when C invoked a method on B, it would have to be marshaled across the context boundary, but then when B executed the method, it would have been in the context of A’s transaction. Much better.

This would work if B were context-bound but without attributes. B of course could have its own context attributes, and these might force B to be in a different context from A. For example, B might have a transaction attribute marked RequiresNew. In this case, when B is created, it gets a new context, and thus can’t be in A’s context. Thus, when A rolled back, B’s work could not be undone. You might mark B with the RequiresNew enumeration value because B is an audit function. When A takes an action on the database, it informs B, which updates an audit trail. You don’t want B’s work undone when A undoes its transaction. You want B to be in its own transaction context, rolling back only its own mistakes, not A’s.

An object thus has three choices. The first option is to be context-agile. A context-agile object operates in the context of its caller. Option two is to be context-bound (accomplished by deriving from ContextBoundObject but having no attributes, and thus operating in the context of the creator). Option three is to be context-bound with context attributes, and thus operate only in the context that matches the attributes.

Which you decide upon depends on how your object will be used. If your object is a simple calculator that can’t possibly need synchronization or transactions or any context support, it is more efficient to be context-agile. If your object should use the context of the object that creates it, you should make that object context-bound with no attributes. Finally, if your object has its own context requirements, you should give it the appropriate attributes.

Marshaling Across Context Boundaries

No proxy is needed when accessing context-agile objects within a single app domain. When an object in one context accesses a context-bound object in a second context, it does so through a proxy, and at that time the two context policies are enforced. It is in this sense that a context creates a boundary; the policy is enforced at the boundary between contexts.

For example, when you mark a context-bound object with the System.EnterpriseServices.Synchronization attribute, you indicate that you want the system to manage synchronization for that object. All objects outside that context must pass through the context boundary to touch one of the objects, and at that time the policy of synchronization will be applied.

Tip

Strictly speaking, marking two classes with the Synchronization attribute doesn’t guarantee that they will end up in the same context. Each attribute gets to vote on whether it is happy with the current context at activation. If two objects are marked for synchronization, but one is pooled, they will be forced into different contexts.

Objects are marshaled differently across context boundaries, depending on how they are created:

  • Typical objects aren’t marshaled at all; within app domains they are context-agile.

  • Objects marked with the Serializable attribute are marshaled by value across app domains and are context-agile.

  • Objects that derive from MarshalByRefObject are marshaled by reference across app domains and are context-agile.

  • Objects derived from ContextBoundObject are marshaled by reference across app domains as well as by reference across context boundaries.

Remoting

In addition to being marshaled across context and app domain boundaries, objects can be marshaled across process boundaries, and even across machine boundaries. When an object is marshaled, either by value or by proxy, across a process or machine boundary, it is said to be remoted.

Understanding Server Object Types

There are two types of server objects supported for remoting in .NET: well-known and client-activated. The communication with well-known objects is established each time a message is sent by the client. There is no permanent connection with a well- known object, as there is with client-activated objects.

Well-known objects come in two varieties: singleton and single-call . With a well- known singleton object, all messages for the object, from all clients, are dispatched to a single object running on the server. The object is created the first time a client attempts to connect to it, and is there to provide service to any client that can reach it. Well-known objects must have a parameterless constructor.

With a well-known single-call object, each new message from a client is handled by a new object. This is highly advantageous on server farms, where a series of messages from a given client might be handled in turn by different machines depending on load balancing.

Client-activated objects are typically used by programmers who are creating dedicated servers, which provide services to a client they are also writing. In this scenario, the client and the server create a connection, and they maintain that connection until the needs of the client are fulfilled.[1]

Specifying a Server with an Interface

The best way to understand remoting is to walk through an example. Here, build a simple four-function Calculator class, like the one used in an earlier discussion on web services (see Chapter 15) that implements the interface shown in Example 19-2.

Example 19-2. The Calculator interface

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Calculator
{
   public interfaceICalc
   {
      double Add( double x, double y );
      double Sub( double x, double y );
      double Mult( double x, double y );
      double Div( double x, double y );
   }
}

Save this in a file named ICalc.cs and compile it into a file named Calculator.dll. To create and compile the source file in Visual Studio, create a new project of type C# Class Library, enter the interface definition in the Edit window, and then select Build on the Visual Studio menu bar. Alternatively, if you have entered the source code using Notepad or another text editor, you can compile the file at the command line by entering:

csc /t:library ICalc.cs

There are tremendous advantages to implementing a server through an interface. If you implement the calculator as a class, the client must link to that class to declare instances on the client. This greatly diminishes the advantages of remoting because changes to the server require the class definition to be updated on the client. In other words, the client and server would be tightly coupled. Interfaces help decouple the two objects; in fact, you can later update that implementation on the server, and as long as the server still fulfills the contract implied by the interface, the client need not change at all.

Building a Server

To build the server used in Example 19-3, create CalculatorServer.cs in a new project of type C# Console Application (be sure to include a reference to Calculator.dll) and then compile it by selecting Build on the Visual Studio menu bar.

The CalculatorServer class implements ICalc . It derives from MarshalByRefObject so that it will deliver a proxy of the calculator to the client application:

class CalculatorServer : MarshalByRefObject, Calculator.ICalc

The implementation consists of little more than a constructor and simple methods to implement the four functions.

In Example 19-3, you’ll put the logic for the server into the Main() method of CalculatorServer.cs.

Your first task is to create a channel . Use HTTP as the transport mechanism. You can use the HTTPChannel type provided by .NET:

HTTPChannel chan = new HTTPChannel(65100);

Notice that you register the channel on TCP/IP port 65100 (see the discussion of port numbers in Chapter 21).

Next, register the channel with the CLR ChannelServices using the static method RegisterChannel:

ChannelServices.RegisterChannel(chan);

This step informs .NET that you will be providing HTTP services on port 65100, much as IIS does on port 80. Because you’ve registered an HTTP channel and not provided your own formatter, your method calls will use the SOAP formatter by default.

Now you’re ready to ask the RemotingConfiguration class to register your well-known object. You must pass in the type of the object you want to register, along with an endpoint. An endpoint is a name that RemotingConfiguration will associate with your type. It completes the address. If the IP address identifies the machine and the port identifies the channel, the endpoint indicates the exact service. To get the type of the object, you can use typeof , which returns a Type object. Pass in the full name of the object whose type you want:

Type calcType =
   typeof( "CalculatorServerNS.CalculatorServer" );

Also, pass in the enumerated type that indicates whether you are registering a SingleCall or Singleton:

RemotingConfiguration.RegisterWellKnownServiceType
   ( calcType, "theEndPoint",WellKnownObjectMode.Singleton );

The call to RegisterWellKnownServiceType creates the server-side sink chain. Now you’re ready to rock and roll. Example 19-3 provides the entire source code for the server.

Example 19-3. The Calculator server

#region Using directives

using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Text;

#endregion

namespace CalculatorServerNS
{
   class CalculatorServer : MarshalByRefObject, Calculator.ICalc
   {
      public CalculatorServer( )
      {Console.WriteLine( "CalculatorServer constructor" );
      }
      // implement the four functions
      public double Add( double x, double y )
      {
         Console.WriteLine( "Add {0} + {1}", x, y );
         return x + y;
      }
      public double Sub( double x, double y )
      {
         Console.WriteLine( "Sub {0} - {1}", x, y );
         return x - y;
      }
      public double Mult( double x, double y )
      {
         Console.WriteLine( "Mult {0} * {1}", x, y );
         return x * y;
      }
      public double Div( double x, double y )
      {
         Console.WriteLine( "Div {0} / {1}", x, y );
         return x / y;
      }
   }

   public class ServerTest
   {
      public static void Main( )
      {
         // create a channel and register it
         HttpChannel chan = new HttpChannel( 65100 );
         ChannelServices.RegisterChannel( chan );

         Type calcType =
            Type.GetType( "CalculatorServerNS.CalculatorServer" );

         // register our well-known type and tell the server
         // to connect the type to the endpoint "theEndPoint"
         RemotingConfiguration.RegisterWellKnownServiceType
            ( calcType,
              "theEndPoint",
               WellKnownObjectMode.Singleton );

         //  "They also serve who only stand and wait." (Milton)
         Console.WriteLine( "Press [enter] to exit..." );
         Console.ReadLine( );
      }
   }
}

When you run this program, it prints its self-deprecating message:

Press [enter] to exit...

and then waits for a client to ask for service.

Building the Client

While the CLR will preregister the TCP and HTTP channel, you will need to register a channel on the client if you want to receive callbacks or you are using a nonstandard channel. For this example, you can use channel 0:

HTTPChannel chan = new HTTPChannel(0);
ChannelServices.RegisterChannel(chan);

The client now need only connect through the remoting services, passing a Type object representing the type of the object it needs (in our case, the ICalc interface) and the Uniform Resource Identifier (URI) of the service.

Object obj =
    RemotingServices.Connect
        (typeof(Programming_CSharp.ICalc), 
        "http://localhost:65100/theEndPoint");

In this case, the server is assumed to be running on your local machine, so the URI is http://localhost, followed by the port for the server (65100), followed in turn by the endpoint you declared in the server (theEndPoint).

The remoting service should return an object representing the interface you’ve requested. You can then cast that object to the interface and begin using it. Because remoting can’t be guaranteed (the network might be down, the host machine may not be available, and so forth), you should wrap the usage in a try block:

try
{
    Programming_CSharp.ICalc calc = 
        obj as Programming_CSharp.ICalc;

    double sum = calc.Add(3,4);

You now have a proxy of the calculator operating on the server, but usable on the client, across the process boundary and, if you like, across the machine boundary. Example 19-4 shows the entire client (to compile it, you must include a reference to Calculator.dll as you did with CalcServer.cs).

Example 19-4. The remoting Calculator client

#region Using directives

using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Text;

#endregion

namespace CalculatorClient
{
   class CalcClient
   {

      public static void Main( )
      {
         int[] myIntArray = new int[3];Console.WriteLine("Watson, come here I need you...");

         // create an Http channel and register it
         // uses port 0 to indicate won't be listening
         HttpChannel chan = new HttpChannel(0);
         ChannelServices.RegisterChannel(chan);

         Object obj = RemotingServices.Connect
            (typeof(Calculator.ICalc), 
            "http://localhost:65100/theEndPoint");

         try
         {
            // cast the object to our interface
            Calculator.ICalc calc = obj as Calculator.ICalc;

            // use the interface to call methods
            double sum = calc.Add(3.0,4.0);
            double difference = calc.Sub(3,4);
            double product = calc.Mult(3,4);
            double quotient = calc.Div(3,4);

            // print the results 
            Console.WriteLine("3+4 = {0}", sum);
            Console.WriteLine("3-4 = {0}", difference);
            Console.WriteLine("3*4 = {0}", product);
            Console.WriteLine("3/4 = {0}", quotient);
         }
         catch( System.Exception ex )
         {
            Console.WriteLine("Exception caught: ");
            Console.WriteLine(ex.Message);
         }
      }
   }
}

Output on client:
Watson, come here I need you...
3+4 = 7
3-4 = -1
3*4 = 12
3/4 = 0.75

Output on server:
Calculator constructor
Press [enter] to exit...
Add 3 + 4
Sub 3 - 4
Mult 3 * 4
Div 3 / 4

The server starts up and waits for the user to press Enter to signal that it can shut down. The client starts and displays a message to the console. The client then calls each of the four operations. You see the server printing its message as each method is called, and then the results are printed on the client.

It is as simple as that; you now have code running on the server and providing services to your client.

Using SingleCall

To see the difference that SingleCall makes versus Singleton, change one line in the server’s Main( ) method. Here’s the existing code:

RemotingConfiguration.RegisterWellKnownServiceType
    ( calcType,
      "theEndPoint",
       WellKnownObjectMode.Singleton );

Change the object to SingleCall:

RemotingConfiguration.RegisterWellKnownServiceType
    ( calcType,
      "theEndPoint",
       WellKnownObjectMode.SingleCall);

The output reflects that a new object is created to handle each request:

Calculator constructor
Press [enter] to exit...
Calculator constructor
Add 3 + 4
Calculator constructor
Sub 3 - 4
Calculator constructor
Mult 3 * 4
Calculator constructor
Div 3 / 4

Understanding RegisterWellKnownServiceType

When you called the RegisterWellKnownServiceType( ) method on the server, what actually happened? Remember that you obtain a Type object for the Calculator class:

Type.GetType("CalculatorServerNS.CalculatorServer");

You then called RegisterWellKnownServiceType(), passing in that Type object along with the endpoint and the Singleton enumeration. This signals the CLR to instantiate your Calculator and then to associate it with an endpoint.

To do that work yourself, you would need to modify Example 19-3, changing Main() to instantiate a Calculator and then passing that Calculator to the Marshal() method of RemotingServices with the endpoint to which you want to associate that instance of Calculator. The modified Main( ) is shown in Example 19-5 and, as you can see, its output is identical to that of Example 19-3.

Example 19-5. Manually instantiating and associating Calculator with an endpoint

public static void Main()
{
   HttpChannel chan = new HttpChannel( 65100 );ChannelServices.RegisterChannel( chan );

   CalculatorServerNS.CalculatorServer  calculator = 
      new CalculatorServer( );
    RemotingServices.Marshal( calculator, "theEndPoint" );

   //  "They also serve who only stand and wait." (Milton)
   Console.WriteLine( "Press [enter] to exit..." );
   Console.ReadLine( );
}

The net effect is that you have instantiated a Calculator object and associated a proxy for remoting with the endpoint you’ve specified (see the “Understanding Endpoints,” section later in this chapter).

You can take that file to your client and reconstitute it on the client machine. To do so, again create a channel and register it. This time, however, open a fileStream on the file you just copied from the server:

FileStream fileStream = 
      new FileStream ("calculatorSoap.txt", FileMode.Open);

Then instantiate a SoapFormatter and call Deserialize( ) on the formatter, passing in the filename and getting back an ICalc:

SoapFormatter soapFormatter = 
   new SoapFormatter ();        
try
{
      ICalc calc=
         (ICalc) soapFormatter.Deserialize (fileStream);

You are now free to invoke methods on the server through that ICalc, which acts as a proxy to the Calculator object running on the server that you described in the calculatorSoap.txt file. The complete replacement for the client’s Main( ) method is shown in Example 19-6. You also need to add two using statements to this example.

Example 19-6. Replacement of Main() from Example 19-4 (the client)

using System.IO;
using System.Runtime.Serialization.Formatters.Soap;

// ...

public static void Main( )
{

   int[] myIntArray = new int[3];

   Console.WriteLine("Watson, come here I need you...");

   // create an Http channel and register it
   // uses port 0 to indicate you won't be listening
   HttpChannel chan = new HttpChannel(0);
   ChannelServices.RegisterChannel(chan);

   FileStream fileStream = 
      new FileStream ("calculatorSoap.txt", FileMode.Open);
   SoapFormatter soapFormatter = 
      new SoapFormatter ( );        

   try
   {
      ICalc calc= 
         (ICalc) soapFormatter.Deserialize (fileStream);

      // use the interface to call methods
      double sum = calc.Add(3.0,4.0);
      double difference = calc.Sub(3,4);
      double product = calc.Mult(3,4);
      double quotient = calc.Div(3,4);

      // print the results 
      Console.WriteLine("3+4 = {0}", sum);
      Console.WriteLine("3-4 = {0}", difference);
      Console.WriteLine("3*4 = {0}", product);
      Console.WriteLine("3/4 = {0}", quotient);
   }
   catch( System.Exception ex )
   {
      Console.WriteLine("Exception caught: ");
      Console.WriteLine(ex.Message);
   }
}

When the client starts up, the file is read from the disk and the proxy is unmarshaled. This is the mirror operation to marshaling and serializing the object on the server. Once you have unmarshaled the proxy, you are able to invoke the methods on the Calculator object running on the server.

Understanding Endpoints

What is going on when you register the endpoint in Example 19-5 (the server)? Clearly, the server is associating that endpoint with the type. When the client connects, that endpoint is used as an index into a table so that the server can provide a proxy to the correct object (in this case, the calculator).

If you don’t provide an endpoint for the client to talk to, you can instead write all the information about your Calculator object to a file and physically give that file to your client. For example, you could send it to your buddy by email, and he could load it on his local computer.

The client can deserialize the object and reconstitute a proxy, which it can then use to access the calculator on your server! (The following example was suggested to me by Mike Woodring, formerly of DevelopMentor, who uses a similar example to drive home the idea that the endpoint is simply a convenience for accessing a marshaled object remotely.)

To see how you can invoke an object without a known endpoint, modify the Main() method of Example 19-3 once again. This time, instead of calling Marshal( ) with an endpoint, just pass in the object:

ObjRef objRef = RemotingServices.Marshal(calculator)

Marshal() returns an ObjRef object. An ObjRef object stores all the information required to activate and communicate with a remote object. When you do supply an endpoint, the server creates a table that associates the endpoint with an objRef so that the server can create the proxy when a client asks for it. ObjRef contains all the information needed by the client to build a proxy, and objRef itself is serializable.

Open a file stream for writing to a new file and create a new SOAP formatter. You can serialize your ObjRef to that file by invoking the Serialize() method on the formatter, passing in the file stream and the ObjRef you got back from Marshal. Presto! You have all the information you need to create a proxy to your object written out to a disk file. The complete replacement for Example 19-5s Main( ) is shown in Example 19-7. You’ll also need to add three using statements to CalcServer.cs, as shown.

Example 19-7. Marshaling an object without a well-known endpoint

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;

public static void Main( )
{
   // create a channel and register it
   HttpChannel chan = new HttpChannel(65100);
   ChannelServices.RegisterChannel(chan);
   // make your own instance and call Marshal directly
   Calculator calculator = new Calculator( );

   ObjRef objRef = RemotingServices.Marshal(calculator);

   FileStream fileStream = 
      new FileStream("calculatorSoap.txt",FileMode.Create);

   SoapFormatter soapFormatter = new SoapFormatter( );

   soapFormatter.Serialize(fileStream,objRef);
   fileStream.Close( );

   //  "They also serve who only stand and wait." (Milton)
   Console.WriteLine(
     "Exported to CalculatorSoap.txt. Press ENTER to exit...");
   Console.ReadLine( );
}

When you run the server, it writes the file calculatorSoap.txt to the filesystem. The server then waits for the client to connect. It might have a long wait. (Though after about 10 minutes, it shuts itself down.)



[1] Client-activated objects can be less robust. If a call fails to a client-activated object, the developer must assume that the object has been lost on the server and must regenerate the object from scratch.

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

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