This article tries to cover all aspects of remoting in a brief way, where the sample project is a very simple example of remoting.
Introduction
.NET Remoting is a mechanism for communicating between objects which are not in the same process. It is a generic system for different applications to communicate with one another. .NET objects are exposed to remote processes, thus allowing inter process communication. The applications can be located on the same computer, different computers on the same network, or on computers across separate networks.
Microsoft .NET Remoting provides a framework that allows objects to interact with each other across application domains. Remoting was designed in such a way that it hides the most difficult aspects like managing connections, marshaling data, and reading and writing XML and SOAP. The framework provides a number of services, including object activation and object lifetime support, as well as communication channels which are responsible for transporting messages to and from remote applications.
What are remote objects? Any object outside the application domain of the caller application should be considered remote, where the object will be reconstructed. Local objects that cannot be serialized cannot be passed to a different application domain, and are therefore non remotable.
Any object can be changed into a remote object by deriving it from MarshalByRefObject
, or by making it serializable either by adding the [Serializable]
tag or by implementing the ISerializable
interface. When a client activates a remote object, it receives a proxy to the remote object. All operations on this proxy are appropriately indirected to enable the Remoting infrastructure to intercept and forward the calls appropriately. In cases where the proxy and remote objects are in different application domains, all method call parameters on the stack are converted into messages and transported to the remote application domain, where the messages are turned back into a stack frame and the method call is invoked. The same procedure is used for returning results from the method call.
There are three types of objects that can be configured to serve as .NET remote objects. You can choose the type of object depending on the requirement of your application.
Single Call
Single Call objects service one and only one request coming in. Single Call objects are useful in scenarios where the objects are required to do a finite amount of work. Single Call objects are usually not required to store state information, and they cannot hold state information between method calls.
Singleton Objects
Singleton objects are those objects that service multiple clients, and hence share data by storing state information between client invocations. They are useful in cases in which data needs to be shared explicitly between clients, and also in which the overhead of creating and maintaining objects is substantial.
Client-Activated Objects (CAO)
Client-activated objects (CAO) are server-side objects that are activated upon request from the client. When the client submits a request for a server object using a "new
" operator, an activation request message is sent to the remote application. The server then creates an instance of the requested class, and returns an ObjRef
back to the client application that invoked it. A proxy is then created on the client side using the ObjRef
. The client's method calls will be executed on the proxy. Client-activated objects can store state information between method calls for its specific client, and not across different client objects. Each invocation of "new
" returns a proxy to an independent instance of the server type.
Domains
In .NET, when an application is loaded in memory, a process is created, and within this process, an application domain is created. The application is actually loaded in the application domain. If this application communicates with another application, it has to use Remoting because the other application will have its own domain, and across domains, object cannot communicate directly. Different application domains may exist in the same process, or they may exist in different processes.
Contexts
The .NET runtime further divides the application domain into contexts. A context guarantees that a common set of constraints and usage semantics govern all access to the objects within it. All applications have a default context in which objects are constructed, unless otherwise instructed. A context, like an application domain, forms a .NET Remoting boundary. Access requests must be marshaled across contexts.
Proxies
When a call is made between objects in the same Application Domain, only a normal local call is required; however, a call across Application Domains requires a remote call. In order to facilitate a remote call, a proxy is introduced by the .NET framework at the client side. This proxy is an instance of the TransparentProxy
class, directly available to the client to communicate with the remote object. Generally, a proxy object is an object that acts in place of some other object. The proxy object ensures that all calls made on the proxy are forwarded to the correct remote object instance. In .NET Remoting, the proxy manages the marshaling process and the other tasks required to make cross-boundary calls. The .NET Remoting infrastructure automatically handles the creation and management of proxies.
RealProxy and TransparentProxy
The .NET Remoting Framework uses two proxy objects to accomplish its work of making a remote call from a client object to a remote server object: a RealProxy
object and a TransparentProxy
object. The RealProxy
object does the work of actually sending messages to the remote object and receiving response messages from the remote object. The TransparentProxy
interacts with the client, and does the work of intercepting the remote method call made by the client.
Marshaling
Object Marshalling specifies how a remote object is exposed to the client application. It is the process of packaging an object access request in one application domain and passing that request to another domain. The .NET Remoting infrastructure manages the entire marshaling process. There are two methods by which a remote object can be made available to a local client object: Marshal by value, and Marshal by reference.
Marshalling Objects by Value
Marshaling by value is analogous to having a copy of the server object at the client. Objects that are marshaled by value are created on the remote server, serialized into a stream, and transmitted to the client where an exact copy is reconstructed. Once copied to the caller's application domain (by the marshaling process), all method calls and property accesses are executed entirely within that domain.
Marshall by value has several implications; first, the entire remote object is transmitted on the network. Second, some or the entire remote object may have no relevance outside of its local context. For example, the remote object may have a connection to a database, or a handle to a window, or a file handle, etc. Third, parts of the remote object may not be serialiazable. In addition, when the client invokes a method on an MBV object, the local machine does the execution, which means that the compiled code (remote class) has to be available to the client. Because the object exists entirely in the caller's application domain, no state changes to the object are communicated to the originating application domain, or from the originator back to the caller.
MBV objects are, however, very efficient if they are small, and provide a repeated function that does not consume bandwidth. The entire object exists in the caller's domain, so there is no need to marshal accesses across domain boundaries. Using marshal-by-value objects can increase performance and reduce network traffic, when used for small objects or objects to which you will be making many accesses.
Marshal by value classes must either be marked with the [Serilaizable]
attribute in order to use the default serialization, or must implement the ISerializable
interface.
Marshalling Objects by Reference
Marshaling by reference is analogous to having a pointer to the object. Marshal by reference passes a reference to the remote object back to the client. This reference is an ObjRef
class that contains all the information required to generate the proxy object that does the communication with the actual remote object. On the network, only parameters and return values are passed. A remote method invocation requires the remote object to call its method on the remote host (server).
Marshal by reference classes must inherit from System.MarshalByRefObject
.
Marshalling Done by the .NET Framework
Marshalling is done by the .NET Framework itself. We don’t have to write any code for it.
Channels
The .NET Remoting infrastructure provides a mechanism by which a stream of bytes is sent from one point to the other (client to server, etc.). This is achieved via a channel. Strictly speaking, it is a class that implements the IChannel
interface. There are two pre-defined .NET Remoting channels existing in System.Runtime.Remoting.Channels
, the TcpChannel
and the HttpChannel
. To use the TcpChannel
, the server must instantiate and register the TcpServerChannel
class, and the client, the TcpClientChannel
class.
Channel selection is subject to the following rules:
- At least one channel must be registered with the remoting framework before a remote object can be called. Channels must be registered before objects are registered.
- Channels are registered per application domain. There can be multiple application domains in a single process. When a process dies, all channels that it registers are automatically destroyed.
- It is illegal to register the same channel that listens on the same port more than once. Even though channels are registered per application domain, different application domains on the same machine cannot register the same channel listening on the same port. You can register the same channel listening on two different ports for an application domain.
- Clients can communicate with a remote object using any registered channel. The remoting framework ensures that the remote object is connected to the right channel when a client attempts to connect to it. The client is responsible for calling the
RegisterChannel
on the ChannelService
class before attempting to communicate with a remote object.
Serialization Formatters
.NET Remoting uses serialization to copy marshal-by-value objects, and to send reference of objects which are marshal-by-reference, between application domains. The .NET Framework supports two kinds of serialization: binary serialization and XML serialization.
Formatters are used for encoding and decoding the messages before they are transported by the channel. There are two native formatters in the .NET runtime, namely binary (System.Runtime.Serialization.Formatters.Binary
) and SOAP (System.Runtime.Serialization.Formatters.Soap
). Applications can use binary encoding where performance is critical, or XML encoding where interoperability with other remoting frameworks is essential.
A key point to remember is that .NET Remoting always uses binary serialization, but you have your choice of output formats. The type of serialization (binary or XML) determines what object data is output. The formatter determines the format in which that data is stored.
The data in binary format has smaller size than in XML format. The smaller size makes binary formatting the obvious choice for intranet applications or whenever network performance is critical. However, not all firewalls look kindly on binary formatted data, so if you're distributing an application that needs to use Remoting over the Internet, you might find yourself forced to use SOAP formatting. Another benefit of SOAP formatting is that it's human readable, which gives you the opportunity to examine message traffic for debugging. Fortunately, Remoting works exactly the same regardless of which formatter you use, so you can use SOAP formatting during initial development and debugging, and then convert to binary format for production release.
.NET Remoting Metadata
.NET Remoting uses metadata to dynamically create proxy objects. The proxy objects that are created at the client side have the same members as the original class. But the implementation of the proxy object just forwards all requests through the .NET Remoting runtime to the original object. The serialization formatter uses metadata to convert method calls to payload stream and back.
The client can obtain the metadata information required to access the remote object in the following ways:
- The .NET assembly of the server object.
- The Remoting object can provide a WSDL (Web Services Description Language) file that describes the object and its methods.
- .NET clients can use the SOAPSUDS utility to download the XML schema from the server (generated on the server) to generate source files or an assembly that contains only metadata, no code.
Object Activation
Object activation refers to the various ways in which a remote object can be instantiated. Marshal by value objects have a simple activation scheme, they are created when the client first requests them. Marshal by reference objects have two activation schemes:
Server Activated Objects
Server Activated Objects are created only when the client makes the first call to the remote method. In other words, when the client asks for the creation of the remote object, only the local proxy is created on the client, the actual remote object on the server is instantiated on the first method call. These proxies can be generated as:
...
RemotingConfiguration.RegisterWellKnownServiceType(
typeof (RemoteServerObject), "Test",
WellKnownObjectMode.SingleCall);
...
IRemoteCom obj = (IRemoteCom)Activator.GetObject(typeof(IRemoteCom),
"tcp://localhost:1002/Test");
...
We have two registration types, Singleton
and SingleCall
, which were explained earlier. The code for both is:
RemotingConfiguration.RegisterWellKnownServiceType( typeof(RemoteServerObject),
"Test", WellKnownObjectMode.SingleCall);
...
RemotingConfiguration.RegisterWellKnownServiceType( typeof(RemoteServerObject),
"Test", WellKnownObjectMode.Singleton);
For Server Activated Objects, the proxy object (on the client) and the actual object (on the server) are created at different times.
Client Activated Objects
These are created on the server immediately upon the client's request. An instance of a Client Activated Object is created every time the client instantiates one, either by the use of new
or Activator.CreateInstance(...)
.
...
RemotingConfiguration.ApplicationName = "TestCAO";
RemotingConfiguration.RegisterActivatedServiceType(typeof(RemoteObjectCAO));
...
RemotingConfiguration.RegisterActivatedClientType(typeof(RemoteObjectCAO),
"tcp://localhost:1002/TestCAO");
obj = new RemoteObjectCAO();
...
In all these examples, "localhost
" can be replaced with an actual remote machine's IP address.
When to use which kind of activation depends upon different scenarios. Some of them are:
- Singleton object reference to the same server object, and any changes made to that object by one client are visible by all of the object's clients. Think of the Windows Desktop, where a change made by one program is visible by all client programs. Persistent data is held on the server, and is accessible by all clients.
- Use the
SingleCall
model to provide a stateless programming model (the traditional Web services request/response model), or any time you have no need to maintain a persistent object state on the server. - Use client activation if the object needs to maintain persistent per-client state information.
Object Lifetime and Leases
A Server Activated Object that has been registered as SingelCall
has a very simple and limited lifetime. It lives for a single call only. Thereafter, it is marked for removal by the Garbage Collector.
The other two types, Singleton and Client Activated Objects, have their lifetimes managed by a service introduced by the .NET Framework, the Leased Based Manager. On the server, the application domain's lease manager determines when a server-side object should be marked for deletion. Each application domain has its own lease manager object. For objects that have object references that are transported outside the application, a lease is created. The lease has a lease time; when the lease reaches zero, it expires, and the object is disconnected from the .NET Remoting Framework. Once all the references to the object within the AppDomain have been freed, the object will be collected when the next garbage collection occurs. A sponsor is another object that plays a role in the life time. When a lease has expired, one or more of the lease's sponsors are invoked by the Lease Manager, where they are given the opportunity to renew the lease. If none of the sponsors decides to renew the lease, the lease manager removes the lease and the object is garbage collected.
The default settings for the lease manager are five minutes for initial time-to-live, poll every ten seconds, and add an additional two minutes for every remote call by a client. All these values are configurable, we can override MarshalByRefObject InitializeLifetimeService()
to define our own values in the remotable object.
The InitializeLifetimeService
method can be overridden as follows:
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromSeconds(25);
lease.SponsorshipTimeout = TimeSpan.FromSeconds(25);
lease.RenewOnCallTime = TimeSpan.FromSeconds(20);
}
return lease;
}
Under this scheme, the remote object's (server-side object) lifetime is independent of the client object's lifetime. The decoupling of the server and the client lifetimes is a very flexible design. Consider, a remote object that is costly to instantiate can be given a long lifetime lease, or can be kept alive by a sponsor. In addition, a remote object that holds scarce or important resources can be given short lifetimes, and be reclaimed by the Garbage Collector quickly. More importantly, this decoupling guarantees that the remote object will have a finite lifetime, regardless of network conditions or the existence of any clients.
Renewing Leases (Increasing Life Times)
While objects have a default lease period, there are many ways to extend the lease period to keep the object alive, in case the client wants to maintain state in the same server object.
- The server object can set its lease time to infinity, which instructs Remoting not to collect it during garbage collection cycles.
- The client can call the
RemotingServices.GetLifetimeService
method to get the lease of the server object from the lease manager of the AppDomain. From the Lease
object, the client can then call the Lease.Renew
method to extend the lease. - The client can register a sponsor for the specific lease with the lease manager of the AppDomain. When the remote object's lease expires, the lease manager calls back to the sponsor to request a lease renewal.
- If the
ILease::RenewOnCallTime
property is set, then each call to the remote object renews the lease by the amount of time specified by the RenewOnCallTime
property.
Once the object has been marshaled, the lease goes from the initial to the active state, and any attempt to initialize the lease properties will be ignored (an exception is thrown). InitializeLifetimeService
is called when the remote object is activated. After activation, the lease can only be renewed.
The .NET Remoting implementation gives you two ways to renew the lease on a remote object. The client can call ILease.Renew
directly, or the client can contact a sponsor and ask the sponsor to renew the lease.
The sponsor object listens for queries from the host system's lease manager, and responds accordingly. Sponsors register with the lease manager by obtaining a reference to the lease and calling ILease.Register
. The lease manager will periodically query the sponsor to see if it wants to renew the lease. If the sponsor wants to renew the lease, it returns a renewal TimeSpan
value in response to the server's query.
Sponsors are preferable to client ILease.Renew
loops, for several reasons. First, the server's lease manager keeps track of when the object's lease is about to expire, and will query the sponsor as needed in order to ensure the opportunity to extend the lease before time runs out. It also is more efficient, both in bandwidth and processing time. In general, where there are multiple clients for a single remote object (as in the case of a singleton), it is more efficient for the lease manager to query a single object for renewal rather than to have each client continually pinging the server. This is also true in the case of client activated objects because it reduces the network traffic to just the minimum required to keep the object alive.
You can have the client itself implement the Isponsor
interface, or you can create a sponsor object that maintains leases for one or more remote objects and clients. Here, I am specifying the code to implement Isponsor
by the client itself.
Implement the Isponsor
in the remotable client class:
class RemoteClient: System.MarshalByRefObject, Isponsor
The Isponsor
interface requires that you implement a single method, Renewal
. The return value is the amount of time, expressed as a TimeSpan
, to extend the lease. Our Renewal
method will extend the lease for an additional 30 seconds. Here's the code, which you should add to the remote client:
public TimeSpan Renewal (ILease lease)
{
return TimeSpan.FromSeconds(30);
}
Finally, register the RemoteClient
object with the server's lease manager. Insert the code below where the server's object instance is being created:
RemoteableClass MyRemotObject = new RemoteableClass();
ILease lease = (ILease)RemotingServices.GetLifetimeService(MyRemotObject);
lease.Register(this);
How to Implement Remoting
In the following discussion, we will discuss how we can implement remoting in our applications. The code given in the discussion is just to show what is necessary in the code. You can get the whole code from the source code files.
Create a Remotable Object
A remotable object is an object that inherits from MarshalByRefObject
.
Create a new C# class library project. Add a class called MyRemotableObject
, and put in the following code. Add a reference to System.Runtime.Remoting
in the project, otherwise the TcpChannel
will not be found. Compile the class to make sure you have everything correct.
MyRemotableObject.cs
namespace RemotableObjects
{
public class MyRemotableObject : MarshalByRefObject
{
public MyRemotableObject()
{
}
public void SetMessage(string message)
{
Cache.GetInstance().MessageString = message;
}
}
}
Create a Server to Expose the Remotable Object
We need to create a server object that will act as a listener to accept remote object requests. For this example, we will use the TCP/IP channel. We first create an instance of the channel, and then register it for use by clients at a specific port. The service can be registered as WellKnownObjectMode.SingleCall
, which results in a new instance of the object for each client, or as WellKnownObjectMode.Singleton
, which results in one instance of the object used for all clients.
It is not necessary to create the server listener if you are planning to use IIS. For obvious reasons, IIS only supports the use of the HttpChannel
. Create a virtual directory for your application, and then put code to register your service in the Application_Start
event.
For our example, we'll go ahead and create a server listener, in case you don't have IIS. Since the service needs to be bound to an available port, for our example, I chose 8080, which is a port that I know to be unused on my computer. You may need to choose a different port depending upon what ports you have available. To see a list of the used ports on your computer, open a command prompt, and issue the command "netstat --a
". It may produce a long listing, so make sure the command prompt buffer sizes are set to allow scrolling. Compile the class to make sure you have everything correct.
Create a new C# Windows Application project. Add a Windows Form called frmRServer
, and paste in the following code. Add a reference to System.Runtime.Remoting
in the project, otherwise the TcpChannel
will not be found. In addition, add a reference to the project containing the MyRemotableObject
, otherwise the code will not compile because it won't know how to find a reference to MyRemotableObject
. Here, I am giving a part of the code. You can get the complete code from the source code files.
namespace RemotableObjects
{
public class frmRServer : System.Windows.Forms.Form, IObserver
{
private System.Windows.Forms.TextBox textBox1;
private MyRemotableObject remotableObject;
private System.ComponentModel.Container components = null;
public frmRServer()
{
InitializeComponent();
remotableObject = new MyRemotableObject();
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(MyRemotableObject),"HelloWorld",
WellKnownObjectMode.Singleton);
RemotableObjects.Cache.Attach(this);
}
}
}
Create a Client to Use the Remotable Object
The client will be very simple. It will connect to the server, create an instance of the object using the server, and then execute the SetString
method.
Create a new C# Windows Application project. Add a Windows Form called frmRCleint
, and paste in the following code. Add a reference to the project containing the SampleObject
, otherwise the code will not compile because it won't know how to find a reference to SampleObject
. Compile the class to make sure you have everything correct. Here, only part of code is given just to show the concept. You can get whole code from the source given:
namespace RemotableObjects
{
public class frmRCleint : System.Windows.Forms.Form
{
private System.Windows.Forms.TextBox textBox1;
MyRemotableObject remoteObject;
private System.ComponentModel.Container components = null;
public frmRCleint()
{
InitializeComponent();
TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
remoteObject = (MyRemotableObject) Activator.GetObject(
typeof(MyRemotableObject),"tcp://localhost:8080/HelloWorld");
}
private void textBox1_TextChanged(object sender, System.EventArgs e)
{
remoteObject.SetMessage(textBox1.Text);
}
}
}
Running the Application
To run the application, compile the Remoteable Object Project object. Compile server application and run it. Compile the client application and run it. Two dialogue boxes will appear: one for the Remoting server, and one for the Remoting client. The Remoting server will listen for the requests. And the Remoting client is ready to pass the message to the server. Now, type any string in the client dialogue box, and the client will send it to the server through Remoting, and the server will display the string in the dialog box of the server application. This is a simple demonstration of easy and understandable Remoting.
Object Configurations
There are two ways to configure remote objects. One is by code, and the other is by using a configuration file, both on the server and the client. The advantage of using a configuration file is we don’t have to compile the code every time we change the configuration, so we can change the configuration of the application at any time without disturbing the code or executables. By convention, the configuration file name has the format executablename.config.
Configuration Files
A typical configuration file includes the following, among other, information:
- Host application information
- Name of the objects
- URI of the objects
- Channels being registered (multiple channels can be registered at the same time)
- Lease time information for the server objects
Here is an example the configuration filename:
Server Remoting Configuration (server.exe.config)
<configuration>
<system.runtime.remoting>
<application name="server">
<service>
<activated type="remote.ServiceClass, remote"/>
</service>
<channels>
<channel ref="http" port="8080">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Client Remoting Configuration (client.exe.config)
<configuration>
<system.runtime.remoting>
<application name="client">
<client url = "http://localhost:8080">
<activated type="remote.ServiceClass, remote"/>
</client>
<channels>
<channel ref="http" port="0">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
To use these configuration files in our Remoting test application, place those files (server.config and client.config) in the directory that contains the server.exe and client.exe programs.
Using application configuration files greatly simplifies the code required to set up a remoting application. For example, the single line shown here takes the place of all the configuration code in the server.cs program's Main
method:
RemotingConfiguration.Configure("server.config");
The configuration files shown set the client activation--each client gets its own object. If you want to configure for Singleton mode, change the <service>
section of the server's configuration file so that it reads:
<service>
<wellknown
type="remote.ServiceClass, remote"
objectUri="ServiceURI"
mode="Singleton"
/>
</service>
And change the <client>
section of the client's configuration file so that it reads:
<client url = "http://localhost:8080">
<wellknown
type="remote.ServiceClass, remote"
url="http://localhost:8080/ServiceURI"
/>
</client>
Configuration Through Code
Server
TcpServerChannel channel = new TcpServerChannel(rs.mPort);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemoteServerObject), "Test",
WellKnownObjectMode.SingleCall);
Client
TcpClientChannel chan = new TcpClientChannel();
ChannelServices.RegisterChannel(chan);
IRemoteCom obj = (IRemoteCom)Activator.GetObject(typeof(IRemoteCom),
"tcp://localhost:1002/Test");
Hosting .NET Remoting Objects
.NET Remoting objects can be hosted in:
- Managed executables
NET Remoting objects can be hosted in any regular .NET EXE or a managed service.
- IIS
Remoting objects can be hosted in the Internet Information Server (IIS). By default, Remoting objects hosted in IIS receive messages through the HTTP channel. For hosting Remoting objects under the IIS, a virtual root has to be created, and a "remoting.config" file has to be copied to it. The executable or DLL containing the remote object should be placed in the bin directory under the directory the IIS root points to. It is important to note that the IIS root name should be the same as the application name specified in the configuration files. The Remoting configuration file is automatically loaded when the first message arrives in the application.
- .NET Component Services
NET Remoting objects can be hosted in the .NET component services infrastructure to take advantage of the various COM+ services like Transactions, JIT, and Object pooling.
MyRemotableObject remoteObject;
private System.ComponentModel.Container components = null;
public frmRCleint()
{
InitializeComponent();
TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
remoteObject = (MyRemotableObject) Activator.GetObject(
typeof(MyRemotableObject),"tcp://localhost:8080/HelloWorld");
}
private void textBox1_TextChanged(object sender, System.EventArgs e)
{
remoteObject.SetMessage(textBox1.Text);
}
Running the Application
To run the application, compile the Remotable Object Project object. Compile the server application and run it. Compile the client application and run it. Two dialog boxes will appear, one for the Remoting server and one for the Remoting client. The Remoting server will listen for the requests. And the Remoting client is ready to pass the message to the server. Now, type in any string in the client dialog box, and the client will send it to the server through Remoting, and the server will display the string in the dialog box of the server application. This is a simple demonstration of easy and understandable Remoting.
History
- 13th July, 2006: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.