Remoting (tutorial) - vilinski/nemerle GitHub Wiki

This is a simple example that shows how to realize remoting in Nemerle. Essentially this is translation of the example presented in the MonoHandbook.

Remoting allow clients to access objects in the server. And thus it is a good abstraction to distribute computing.

In this example both Server and Client share the definition of ServerObjects. The server program opens a port for conection and publish a ServerObject. Later the client program connects himself to the server port and access, remotely, the published objects and their methods.

The example shows the basic remoting usage, managing objects by reference and by value (through serialization).

Table of Contents

Client

Save the following code in the file "Client.n".

using System;
using System.Net;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingTest
{
    class RemotingClient
    {
        static Main () :void
        {
            def ch = TcpChannel (0);
            ChannelServices.RegisterChannel (ch);

            // This gets the object from the server (a list of ServerObject)

            Console.WriteLine ("Getting instance ...");
            def remOb = Activator.GetObject (typeof (ServerList),
                                             "tcp://localhost:1122/test.rem");

            def list = remOb :>ServerList;

            // These are remote calls that return references to remote objects

            Console.WriteLine ("Creating two remote items...");
            def item1 = list.NewItem ("Created at server 1") :ServerObject;
            item1.SetValue (111); // another call made to the remote object
            def item2 = list.NewItem ("Created at server 2") :ServerObject;
            item2.SetValue (222);
            Console.WriteLine ();


            // Two objects created in this client app

            Console.WriteLine ("Creating two client items...");
            def item3 = ServerObject ("Created at client 1");
            item3.SetValue (333);
            def item4 = ServerObject ("Created at client 2");
            item4.SetValue (444);
            Console.WriteLine ();

            // Object references passed to the remote list

            Console.WriteLine ("Adding items...");
            list.Add (item3);
            list.Add (item4);
            Console.WriteLine ();


            // This sums all values of the ServerObjects in the list. The server
	    // makes a remote call to this client to get the value of the
	    // objects created locally
            Console.WriteLine ("Processing items...");
            list.ProcessItems ();
            Console.WriteLine ();

            // Passing some complex info as parameter and return value

            Console.WriteLine ("Setting complex data...");
            def cd = ComplexData (AnEnum.D, array ["some" :object, 22, "info"]);
            def res = list.SetComplexData (cd) :ComplexData;
            Console.WriteLine ("This is the result:");
            res.Dump ();
            Console.WriteLine ();

            list.Clear ();
            Console.WriteLine ("Done.");

            ch.StopListening (null);

        }
    }
}

Server

Save the following code in the file "Server.n".

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingTest
{
    public class RemotingServer
    {
        static Main () :int
        {
            Console.WriteLine ("Starting Server");
            def ch = TcpChannel (1122);
            ChannelServices.RegisterChannel (ch);

            def ser = ServerList ();
            _ = RemotingServices.Marshal (ser, "test.rem");

            Console.WriteLine ("Server Running ...");
            _ = Console.ReadLine ();

            ch.StopListening (null);

            0;
        }
    }
}

ServerObjects

Save the following code in the file "ServerObjects.n".

using System;
using System.Runtime.Remoting;
using System.Collections;

namespace RemotingTest
{
    // A list of ServerObject instances
    public class ServerList :MarshalByRefObject
    {
        values : ArrayList;

        public this ()
        {
            values = ArrayList ();
        }

        public Add (v :ServerObject) :void
        {
            _ = values.Add (v);
            System.Console.WriteLine ("Added " + v.Name);

        }

        public ProcessItems () :void
        {
            System.Console.WriteLine ("Processing...");

            mutable total = 0;
            foreach (ob in values) total += (ob :> ServerObject).GetValue ();

            System.Console.WriteLine ("Total: " + total.ToString());
        }

        public Clear () :void
        {
            values.Clear ();
        }

        public  NewItem (name :string) :ServerObject
        {
            def obj = ServerObject (name);
            Add (obj);
            obj;
        }

        public SetComplexData (data :ComplexData) :ComplexData 
        {
            System.Console.WriteLine ("Showing content of ComplexData");
            data.Dump ();
            data;
        }
    }

    // A remotable object
    public class ServerObject :MarshalByRefObject
    {
        mutable _value :int;
        _name :string;

        public this (name :string)
        {
            _name = name;
        }

        public Name :string 
        {
            get { _name; }
        }

        public SetValue (v :int) :void
        {
            System.Console.WriteLine ("ServerObject " + _name + ": setting " + v.ToString() );
            _value = v;
        }

        public GetValue () :int
        {
            System.Console.WriteLine ("ServerObject " + _name + ": getting " + _value.ToString());
            _value;
        }
    }

    public enum AnEnum { |A |B |C |D |E };

    [Serializable] 
    public class ComplexData
    {
        public Val :AnEnum;
        public Info : array [object];

        public this ( va:AnEnum, info : array [object]) 
        {
            Info = info;
            Val = va;
        }

        public  Dump () :void
        {
            System.Console.WriteLine ("Content:");
            System.Console.WriteLine ("Val: " + Val.ToString());
            foreach (ob in Info) System.Console.WriteLine ("Array item: " + ob.ToString());
        }
    }
}

Compilation

Save the following code in the file "Makefile". <bash>all: ncc -t:library ServerObjects.n -o ServerObjects.dll ncc -r:ServerObjects.dll -r:System.Runtime.Remoting.dll Client.n -o Client.exe ncc -r:ServerObjects.dll -r:System.Runtime.Remoting.dll Server.n -o Server.exe </bash>

Run "make" to compile the programs. Then execute the "Server.exe" in a console and "Client.exe" in another one.

Serializing Variants

By default custom attribues on variants are not passed to its variant options.

Thus using

[Serializable] 
public variant AnEnum { |A |B |C |D |E };

instead of the proposed enum (which is directly serializable) will create an System.Runtime.Serialization.SerializationException error - you must explicitly declare each element of the variant as Serializable.

[Serializable] 
public variant AnEnum { 
	[Serializable] | A 
	[Serializable] | B 
	[Serializable] | C { x : string; }
	[Serializable] | D { x : int; }
	[Serializable] | E 
};

Using a macro to mark all options with attribute

It is possible to use a macro Nemerle.MarkOptions to realize this in a compact way. For a little insight of how it works, there is its a full source, which fortunately is short.

using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;

[Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance,
                     Nemerle.MacroTargets.Class)]
macro MarkOptions (t : TypeBuilder, attribute)
{
  // iterate through members of this type and select only variant options
  foreach (ClassMember.TypeDeclaration
            (TopDeclaration.VariantOption as vo) in t.GetParsedMembers ())
  {
    // add given custom attribute to this variant option
    vo.AddCustomAttribute (attribute)
  }
}

Now you can use

[Serializable] 
[Nemerle.MarkOptions (Serializable)]
public variant AnEnum { 
	| A 
	| B 
	| C { x : string; }
	| D { y : int; }
	| E 
}

Side note Remember that if you use variant instead of enum, the expression

AnEnum.B
``` 
has to be changed to 
```nemerle
AnEnum.B ()

This is for keeping consistency with ```nemerle AnEnum.C ("Ala") ```

⚠️ **GitHub.com Fallback** ⚠️