.NET Remoting Quirks and Oddities

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

by Brent Rector of Wintellect

.NET Remoting has numerous advantages in compared to prior technologies, such as DCOM, but .NET Remoting has a few of its own tricky aspects and quirks. In this article, I will concentrate on the implications of .NET’s pervasive use of metadata and its effects on .NET Remoting clients and servers.

Defining a managed data type produces an assembly that contains the type’s executable code plus metadata that describes the type. Referencing that data type from a client assembly causes the client assembly to depend on the type’s assembly. When executing the client assembly, the .NET runtime detects a reference to a type defined in an external assembly, and then proceeds to locate and load the external assembly in order to read the metadata that describes the type.

This dependency isn’t normally a problem for traditional applications. However, when using .NET remoting, the client assembly may be on one machine and the data type’s assembly is on a server. “Issues” arise; the most common one being that the assembly on the server that defines the data type is not available on the client system.

// Server class
public class Calculator {
  private double accumulator;

  public void   Add (double arg) { accumulator += arg; }
  public void   Sub (double arg) { accumulator -= arg; }
  public double Accum            { get { return accumulator; } }
}

// Client class
public class ClientApp {
  static void Main () {
    Calculator c = new Calculator ();
    c.Add (20);
    c.Add (22);
    System.Console.WriteLine ("Total is {0}", c.Accum);
  }
}

One way to address this problem is to use interfaces. Define one or more interfaces in an independent assembly. Have the server data type implement the interface(s). This produces a dependency in the server assembly on the interface assembly. Therefore you’ll have to deploy both the data type’s assembly and the interface assembly on the server machine.

// Defined in the interface assembly
public interface IBasicMath {
    void   Add (double arg);
    void   Sub (double arg);
    double Accum { get; }
}

// Defined in the server assembly
public class Calculator : IBasicMath {
  private double accumulator;

  public double Add (double arg) { accumulator += arg; }
  public double Sub (double arg) { accumulator -= arg; }
  public double Accum            { get { return accumulator; } }
}

Now remove all references in the client assembly to the server data type. There are typically two categories of type references in a client application: requests for a type (local variable declaration, instantiation via new, etc.) and member access to an instance of a certain type (using a field or method of the type).

// Defined in the client assembly
public class ClientApp {
  static void Main () {
    IBasicMath c = /* Get a reference to the object somehow */
    c.Add (20);
    c.Add (22);
    System.Console.WriteLine ("Total is {0}", c.Accum);
  }
}

The second case is easy to handle. Change client references to a member of a type to use the appropriate interface methods as I did in the above example. This harder problem is how to get a reference to an instance of an object without specifying the type of the object.

This happens to be exactly how server-activated types work in .NET remoting. When accessing a server-activated type from a client application, you tell the remoting infrastructure that you want it to build a proxy for a specified type and to connect the proxy to a specified endpoint. Most often, people start by using it this way:

System.Object o =
  System.Activator.GetObject (
     typeof (Calculator),                        // Type of proxy
     http://www.wintellect.com/Math/SharedCalc); // Endpoint

However, it’s perfectly legal to request the remoting infrastructure to build a proxy for an interface. So you can change the code to the following and never mention the Calculator class at all:

System.Object o =
  System.Activator.GetObject (
     typeof (IBasicMath),                        // Type of proxy
     http://www.wintellect.com/Math/SharedCalc); // Endpoint

Using this technique, you can now deploy only the client assembly and the interface assembly to the client system. The assembly containing the Calculator data type does not need to be present on the client. The client application now looks like this:

public class ClientApp {
  static void Main () {
   System.Object o = System.Activator.GetObject (
     typeof (IBasicMath),                        // Type of proxy
     http://www.wintellect.com/Math/SharedCalc); // Endpoint
   IBasicMath c = (IBasicMath) o;
   c.Add (20);
   c.Add (22);
   System.Console.WriteLine ("Total is {0}", c.Accum);
  }
}

It’s important to realize that this technique only works for server-activated well-known types; both single call activation and singleton activation. Note that the method is named “GetObject.” The client retrieves a reference to the instance of whatever class resides at the specified endpoint. The server can even change the type at the endpoint, for example from Calculator to TrigCalculator, and as long as the new class implements the IBasicMath interface, the client code continues to work.
However, you cannot use this technique to access a client-activated type. Client activation does not access a preexisting object at a known endpoint. Instead, client activation sends a request from the client to a generic (type-independent) server endpoint.

public class ClientApp {
  static void Main () {
    System.Object[] activationAttributes = new object[1];
    activationAttributes [0] = new
          System.Runtime.Remoting.Activation.UrlAttribute
                        ("http://www.wintellect.com/Math");

    System.Object o =
          System.Activator.CreateInstance(typeof (Calculator),
                                          null,
                                          activationAttributes);
    Calculator c = (Calculator) o;
    c.Add (20);
    c.Add (22);
    System.Console.WriteLine ("Total is {0}", c.Accum);
  }
}

The request specifies the type of object the server should instantiate. The server creates an instance of the specified type, creates a new endpoint, registers the object to respond to requests at that endpoint, then returns an ObjRef to the client. The remoting layer on the client uses the information in the ObjRef to build a proxy for the specified type and connect the proxy to the endpoint at which the new instance is listening.

About the Author

Brent Rector has designed and written operating systems, compilers and many, many applications. He began developing Windows applications using a beta version of Windows 1.0. Brent is the founder of Wise Owl Consulting, Inc. (http://www.wiseowl.com), a Windows software development and consulting firm and is an instructor for Wintellect (http://www.wintellect.com).
Brent has developed the premier .NET code obfuscator, Demeanor for .NET, Enterprise Edition. He has also written several books on Windows programming, including ATL Internals, Win32 Programming and others.

# # #

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read