My hobbyist coding updates and releases as the mysterious "Mr. Tines"

Showing posts with label erlang. Show all posts
Showing posts with label erlang. Show all posts

Saturday, 9 May 2009

Integrating Scala with Erlang -- Initial steps

So, I've started a parallel implementation of jinterface 1.5 in Scala, the idea being

  1. To teach myself Scala by doing
  2. To experiment with the actor-based concurrency as a counterparty to the Erlang model
  3. To experiment with Scala as a dual VM technology

So far, just the term-representation classes, and the start of the Input/Decode and Output/Encode part; and some unit tests, only for Atom so far.

I am impressed at how much I've been able to do without bringing in platform types that I will need to abstract out. What was memory-based I/O in Java or C# becomes Seq[Byte] or Queue[Byte] as appropriate and basing all exceptions off the shared name Exception root class gets rid of any dependence on concrete subclasses. It's only BigInt that is a known problem so far, where I'll need to factor out a platform abstraction later. Of course abstracting ZLib support is a task lurking in the near future, as well.

The first noticeable difference from trying to tidy the C#2 port of an older Java version is that the functional style and rich library support make a lot of previously long-winded support methods significantly more expressive. Contrast the Erlang list (of integer values) to String

public System.String StringValue
        {
            get
            {
                char[] body = new char[Arity];
                ReadOnlyCollection<IObject> e = Elements;
                for (int i = 0; i < Arity; ++i)
                {
                    Integer v = e[i] as Integer;
                    if (null == v) body[i] = '?';
                    else
                    {
                        System.Diagnostics.Debug.Assert(v != null && v.Value != null);
                        char? c = v.Value.ToChar();
                        if (null == c) c = '?';
                        body[i] = (char)c;
                    }
                }
                return new string(body);
            }
        }
// and elsewhere
        public char? ToChar()
        {
            try
            {
                return (char)System.UInt16.Parse(this.ToString(), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture);
            }
            catch (OverflowException)
            {
                return null;
            }
        }

with

def AsString =
  {
    (Elements map List.AsChar).mkString
  }
// and elsewhere
  def AsChar(obj : IObject) =
    obj match
    {
      case c : Integer if c.Value < (Integer.Zero + Char.MinValue.toInt) => '?'
      case c : Integer if c.Value >= (Integer.Zero + Char.MaxValue.toInt) => '?'
      case c : Integer => c.Value.intValue.toChar
      case _ => '?'
    }

Yes, using C#3's functional extensions, that too could be simplified; this is really the Java equivalent of doing that.

Code drop on the MediaFire page.

Monday, 27 April 2009

ZLIB and .Net

A recurring problem in several of the projects I have in the pipeline is the matter of handling ZLIB. Java, through java.util.zip offers ZLIB compression with a 32k byte window (but no means of tuning the window) with the DeflaterOutputStream. The .Net framework doesn't offer direct ZLIB at all, but provides naked Deflate via System.IO.Compression.DeflateStream.

That gives us enough to be able to reflate the output of a ZLIB deflation, since a ZLIB is a 2 byte header, a deflate section and finally a 4 byte checksum:

import clr    
clr.AddReference( 'vjslib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
from java.io import *
from java.util.zip import *
from System.IO import *
from System.IO.Compression import *
from System import Array, Byte, SByte, Int64
from System.Text import Encoding

def readFileToSByteArray(name):
  # open a file via Java I/O
  raw = RandomAccessFile(name, 'r')
  # print "File size in bytes = %d "  % (raw.length())

  # read into signed byte array
  dim = Array[Int64]([raw.length()])
  helper = Array[SByte]([0])
  buffer = Array.CreateInstance(helper[0].GetType(), dim)
  raw.readFully(buffer)
  raw.close()
  return buffer
  
def sbyteArrayToUbyteArray(buffer, offset=0, length=-1):
  if length < 0:
    length = buffer.Length
 
  dim = Array[Int64]([length])
  helper = Array[Byte]([0])
  ubuffer = Array.CreateInstance(helper[0].GetType(), dim)
  
  # copy into unsigned byte array
  for i in range(0,length):
    ubuffer[i] = buffer[i+offset] & 0xff
  return ubuffer
  
def jdeflate(buffer):
  sink0 = ByteArrayOutputStream()
  sink = DeflaterOutputStream(sink0)
  sink.write(buffer, 0, buffer.Length)
  sink.close()
  sink0.close()
  return sink0.toByteArray()
  
def update_adler(adler, buffer):
  s1 = adler & 0xffff
  s2 = (adler >> 16) & 0xffff
  BASE = 65521
  for n in range(0,buffer.Length):
          s1 = (s1 + buffer[n]) % BASE
          s2 = (s2 + s1)     % BASE
  return (s2 << 16) + s1
  
def ninflate(buffer):
  mem = MemoryStream(buffer)
  inflate = DeflateStream(mem, CompressionMode.Decompress)

  mem = MemoryStream()
  while True:
    x = inflate.ReadByte()
    if x < 0:
      break
    mem.WriteByte(x)
 
  inflate.Close()
  mem.Close()
  return mem.ToArray()
  
 
##==========================================

# select a file
name = ...

# make signed, unsigned buffers and string
buffer = readFileToSByteArray(name)
print "Constructed buffer size %d" % (buffer.Length)
ubuffer = sbyteArrayToUbyteArray(buffer)
instring = Encoding.Default.GetString(ubuffer)

# compute the Adler32 checksum
adler = update_adler(1, ubuffer)
print "Adler32 = %d" % (adler)
 
# deflate in Java
deflated = jdeflate(buffer)

# check the ZLIB header -- expect 120, 156 actually (120,-100)
first = (deflated[0] & 0xff)
second = (deflated[1] & 0xff)
print "header value EXPECTED 120:156 ACTUAL %d:%d" % (first, second)

i = deflated.Length-4
x = ((deflated[i]&0xff) << 24) | ((deflated[i+1]&0xff) << 16) | ((deflated[i+2]&0xff) << 8) | (deflated[i+3]&0xff)
print "Java Adler32 = %d" % (x)

# discard header and Adler32 tail
newbuffer = sbyteArrayToUbyteArray(deflated, 2, deflated.Length-6)
print "ZLIB length %d" % (deflated.Length)
print "deflate section length %d" % (newbuffer.Length)

# reflate
out = ninflate(newbuffer)

# compare with input
print "reflated buffer length : %d" % (out.Length)
outstring = Encoding.Default.GetString(out)
same = outstring.Equals(instring)
print "Output == Input ?? %s" %(str(same))

That works fine; but the converse, taking a .Net deflate and adding the appropriate top and tail took a bit of getting to work.

First pitfall -- the InflaterInputStream has to be read in chunks as large as feasible, rather than byte at a time, so as not to throw a premature EOFException. That overcome, I got a result of the right length, but differing in the final few characters for the file under test, which I resolved by doing a belt-and-braces closing of the deflate operation. The un-refactored code I currently have continues from the above as:

# now the other way around
sink0 = MemoryStream()
sink = DeflateStream(sink0, CompressionMode.Compress)
sink.Write(ubuffer, 0, ubuffer.Length)
## overkill the flush and close here
sink.Flush()
sink.Close()
sink0.Flush()
sink0.Close()

deflated = sink0.ToArray()
print "deflate section length %d" % (deflated.Length)

#now inflate
dim = Array[Int64]([deflated.Length+6])
jbuffer = Array.CreateInstance(buffer[0].GetType(), dim)

jbuffer[0] = 120 # 0111 0100 = 120 : 32 kbit window, Deflate
jbuffer[1] = -100 # 10 0 ????? = 128 + x : default compress, no dict, checksum

def sbyte(x):
  y = x & 0xff
  if y > 127:
    return y - 256
  return y
 
for i  in range(0,deflated.Length):
    jbuffer[i+2] = sbyte(deflated[i])
 
i = deflated.Length+2
jbuffer[i] = sbyte(adler>>24) 
jbuffer[i+1] = sbyte(adler>>16) 
jbuffer[i+2] = sbyte(adler>>8)
jbuffer[i+3] = sbyte( adler )
 
 
source0 = ByteArrayInputStream(jbuffer)
source = InflaterInputStream(source0)

dim = Array[Int64]([ubuffer.Length])
helper = Array[SByte]([0])
xbuffer = Array.CreateInstance(helper[0].GetType(), dim)

## take big bites
offset = 0
try:
 while True:
  x = source.read(xbuffer, offset, xbuffer.Length-offset)
  if x < 0:
    break
  offset += x
except EOFException:
  pass

reflated = sbyteArrayToUbyteArray(xbuffer)

print "reflated length is %d" % (reflated.Length)
outstring = Encoding.Default.GetString(reflated)

same = outstring.Equals(instring)
print same

This should allow me to simplify the baggage accumulated for the C#/Erlang bridge, which currently uses a second-generation port of the original 'C' ZLIB for this sort of interoperation.

Much Later — I have ported this to F# on .net 4.5.



There's a lot of buffer to stream to buffer conversion involved -- the {n,j}{in,de}flate functions are just that. It's ndeflateZLIB, ninflateZLIBFull that do the complete transformation from a byte array to a compressed byte array and back again. The sbyte bit is purely a J# compatibility thing. Full solution here.

Friday, 8 August 2008

Integrating .Net with Erlang, part 8

Gratuitous update on 08/08/08 with a small increment of unit testing -- now using Rhino Mocks 3.4 as well as home-grown ones, and NUnit 2.5 (alpha 3) for the neat

Assert.That( [delegate expression], Throws.Exception<[type]>())
syntax.

Drop here, as usual.

Saturday, 7 June 2008

Erlang and Python

Following the suggestion that I try TwOtp, I've tried the "kicking the tyres" with that library.

Adapting the sample code to work against my own local node seemed easy enough:

from twisted.internet import reactor
from erlang import OneShotPortMapperFactory, buildNodeName

def gotConnection(inst):
    return inst.factory.callRemote(inst, "file", "get_cwd"
        ).addCallback(gotResult)

def gotResult(resp):
    print "Got response", resp
    reactor.stop()

def eb(error):
    print "Got error", error
    reactor.stop()

epmd = OneShotPortMapperFactory('client@chloe.ravnaandtines.com', "cookie")
epmd.connectToNode("servernode@chloe.ravnaandtines.com").addCallback(gotConnection).addErrback(eb)
reactor.run()

which involves rather more baggage than using IronPython against my tidying of the C# port of jinterface, for just a "hello world!", given that the jinterface-derived "hello world!" actually does more work (passing arguments, testing results):

import clr
clr.AddReference("OtpErlang.dll")
from System import Array

from Tinesware.Otp import *
import Tinesware.Otp.Erlang as Erlang

cNode = Self("clientnode@chloe.ravnaandtines.com", "cookie")
sNode = Peer("servernode@chloe.ravnaandtines.com")
connection = cNode.Connect(sNode)

args = Array[Erlang.IObject]([Erlang.Integer(1), Erlang.Integer(2)])

connection.SendRpc("mathserver", "add", args)
sum = connection.ReceiveRpc()

if  sum.Value.ToInt32() != 3:
  print "Assertion failed, returned = " + sum.Value
else:
  print "OK!"

And, like py_interface, I find that this doesn't "just work" either:

C:\Documents and Settings\Steve\My Documents\code\babel\erlang>python twotphw.py

Got error [Failure instance: Traceback (failure with no frames): <class 'erlang.epmd.NodeNotFound'>: 1]

which will require a similar amount of rolling sleeves up and getting hands dirty to resolve.

Friday, 6 June 2008

Erlang with Python and Ruby

Having played a bit with Erlang for real, in the sense of something non-trivial, even if not a complete product, I find I'm in the mood for more. But while it comes with some UI widgets, it's something more for headless use, and some client(s) in other languages doing the display.

To date, I can easily do Swing or WinForms as the UI toolkit -- but what about wxPython or FXRuby?

There are a number of bridges that I've found, but the closest to jinterface seem to be:

Neither of them have "just worked" for me in the way that the Java and C# versions have done against the simple mathserver example. Some of it might be down to my not figuring how the differences in the APIs are meant to work.

For py_interface, I had to comment out the line

[__import__(item) for item in __all__]
from init.py to get imports to work at all; and even then, the simple

from otp import *

def Handler(Result):
    print Result

cNode = erl_node.ErlNode("clientnode@chloe.ravnaandtines.com", erl_opts.ErlNodeOpts(cookie="cookie"))
print cNode
print cNode.Publish()
mBox = cNode.CreateMBox()
print mBox
mBox.SendRPC("servernode@chloe.ravnaandtines.com", 
    "mathserver", "add", [1, 2], Handler)
raw_input("wait")

just sits there; while the Ruby code

require 'erlang_node'
 
s_node = Erlang::Node.new("servernode@chloe.ravnaandtines.com")
c_node = Erlang::LocalNode.new("clientnode@chloe.ravnaandtines.com", "cookie")
connection = Erlang::Connection.new(s_node, c_node)
connection.sendRPC("mathserver", "add", [1,2])
sum = connection.receiveRPC()
puts "Fail" unless sum == 3

throws a different exception every time I run it, from some place or other inside the connection initializer.

So they both need deeper inspection than this simple kicking of the tyres, maybe modifying for R12 nodes, maybe just reverse engineering the kinks out of the driver programs.

Tuesday, 27 May 2008

Integrating .Net with Erlang, part 7

Another update, after work during the holiday weekend.

By using a lot of spare cycles, I found a test case for the division code not previously exercised:

9659964175213286916418497243082984930780677040655778513759952405592597918851617939992802011783376153721017703858175 / 3248063261698017931917474008545947342556579729240422363527 = 2974068974926080862795323621370295193198999568425344221053

Also made the astoundingly obvious discovery that I needed the real Erlang port mapper dæmon running, so put a unit test in which spawns an Erlang process to contact. It works fine in NUnit-gui, but fails every time in the build time coverage plus test cycle, so is labelled as [Explicit]. I need to to write at least one test that handles the call in the other direction, but this is getting close to as far as can easily be done in unit test form.

And it's getting close to my usual summer break from coding in the evenings, so this is probably close to being "it" for a while.

Tuesday, 20 May 2008

Integrating .Net with Erlang, part 6

Time for a major drop of the code.

It's been tidied up to remove most of the deprecation and to start unifying the comments as .Net XML. The BigInteger class has had a number of bugs found and fixed by unit testing (initialization, big left shifts, hex-string), and been made into a struct to reflect its value nature.

Pretty much all (say, 80%) of the stuff that can be tested statically in isolation has been, and the code has been revised with dependency injection and interfaces to do that (including a bug-fixed, back ported (to C# 2.0) version of NullObject.For<T>). Further testing will require some more extensive test set-up, such as spinning up a Java peer in a separate thread and talking TCP with it.

Code coverage is currently just over 60% for the Otp library, just over 70% for the ZLib code, for the static testing; beyond upgrading the wire types and the constants, the upgrade to 1.4 spec from 1.2 is not really begun.

Wednesday, 23 April 2008

Integrating .Net with Erlang, part 5

I have a first drop that has all the C# input code written, and a start of some unit tests for interoperability with the J# build of jinterface 1.4. Nowhere near enough tests are written yet, and a couple of those that have been (encoding a 64-bit integer in J# and unpacking in C#) don't yet work. But at least it's a start.

Still to do are the full gamut of integer types, and the complex ones -- lists, tuples, ports and such, then replicate for writing by C# and reading in J#.

One fix to the tests was for my code -- I needed to take the surprising little-endian wire format for an extended integer, and reverse it to fit into the BigInteger class I got from Codeplex; the second was a fix to that class:

381c381,384
<    
---
> 
>             // BUGFIX -- use an accumulator rather than |=ing into
>             // m_digits[m_digits.DataUsed], which will be zero
>             DType accumulator = 0;
384c387,389
<     m_digits[m_digits.DataUsed] |= (DType)(array[offset + leftOver - i] << ((i - 1) * 8));
---
>                 DType digit = array[offset + leftOver - i];
>                 digit = (digit << ((i - 1) * 8));
>                 accumulator |= digit;
385a391
>             m_digits[m_digits.DataUsed] = accumulator;

Code drop for the curious at the usual site. The project assumes you have NCover and NUnit around your system to run the tests and take coverage stats.

Update: More tests, and a few more fixes, refactorings and extensions added 24-Apr. Doubles now get encoded in the new style and integers that fall into the Int64 range are handled so the jinterface code can handle them.

Update: 26-Apr -- Added an export to little-endian byte array for BigInteger and added unit tests for short and long BigInteger representations.

Update: 27-Apr -- A first pass of Java type to C# and back for all the Java wire types, and a start of interface breaking changes to shut FxCop up a bit. Still needs a lot of like-for-like based unit tests, and detailed code cleaning.

Update: 28-Apr -- Unit tests about complete for all the wire types. Now the fun really begins.

Update: 4-May -- Unit tests extended to the input and output classes, including adding compression via a modified version of the ComponentAce port JZlib. All code now clean after FxCop scrubbing. Still needs all the node related classes being compared with the jinterface 1.4 versions and tested.

Tuesday, 22 April 2008

Integrating .Net with Erlang, part 4

So where are we going from here with the Otp.Net code? First off, if we bring the codebase into .Net 2.0 from its '04-'06 vintage, and make all the data classes in the Otp.Erlang namespace immutable -- returning ReadOnlyConnection(of T)rather than arrays as needed -- we can get rid of the ICloneable support. That lets us make Otp.Erlang.Object into an interface containing just the encode method(dropping the decode method, or making it an extension method as per C# 3.0).

Then we can use some subset of the fields used in equality tests to do GetHashCode properly, and replace arrays with growable collections.

Next, fix the range check on Double's floatValue method, and make it return a nullable value:

    public float? floatValue()
    {
        if (d > Single.MaxValue || d < Single.MinValue)
            return null;

        return (float) d;
    }

and rename Long as Integer, kill both their derived classes and make Integer carry a BigInteger to which we can delegate conversions returning nullables.

That will mean some fixing up of input and output, but we need that anyway to handle new-style float values encoded as 8-byte big-endian blobs (and the change of tense means that this is where we move past what I've done so far).

To come after that will be making GenericQueue delegate to Queue(of IObject), and then a general scrub done in comparison with jinterface 1.4, before it will be sorta-releasable.

At the moment it builds, but will not work, so no interim archive is yet publicly available.

Saturday, 19 April 2008

Integrating .Net with Erlang, part 3

A kind anonymous has pointed out that someone has already done the job of porting an earlier version (1.2.1.1) of the jinterface code, even though it has not made its way into the standard distro. Via this blog post, one eventually gets to the sourceforge repository details for it.

This actually does work with IronPython (after the trivial edits to load the appropriate assembly and the slightly different class names) and without the J# runtime being involved.

I spotted the comment after spending a little while porting the 1.4 code. Actually the underlying Java code is quite horrid -- to start with there are lots of methods with the same name as fields; and a lot of identically named fields shadowing ones elsewhere in the inheritance hierarchy; and methods and fields are used interchangeably. Then there are blocks of cut-and-paste and tweak code; plenty of swallowing of exceptions and blurred responsibilities (where one object performs a series of manipulations on a member of another object). And there there is this (where d is a double):

  public float floatValue()
    throws OtpErlangRangeException {
    float f = (float)d;

    if (f != d) {
      throw new OtpErlangRangeException("Value too large for float: " + d);
    }

    return f;
  }

which doesn't look too tested to me -- what's this (f != d), in case you couldn't spot it. This is carried over into otp.net, and, I think, shows how little floating point is used.

I fixed up the name clashes and some of the blurred responsibility in the Java, then ran the conversion over; and by taking out the BigInteger stuff (not there in otp.net's Long class -- haven't checked the rest of these), compression, most of the floating point, got what was left to build, though it currently fails in the challenge/response handshake (where otp.net has replaced the roll-your-own MD5 with calls into System.Cryptography).

Where could we go from here?

Two main things need doing. The otp.net code needs to be brought up to the 1.4 standard and make better use of .Net 2.0 e.g. for tracing; and it needs to be refactored to clean up inherited code smells.

Ideally, some of the changes should be back-ported into jinterface, too.

To date, this includes fallback to Java's file based location for cookie and a replacement for some of the non-ported tracing code (logging the remote endpoint).

Friday, 18 April 2008

Integrating .Net with Erlang, part 2

So, we have pure J#. Where do we go from here?

C#

The body of the driver class carries over unchanged but for the Exception type. The driver project has to itself reference vjslib, as well as the jinterface assembly but is otherwise a standard C# class-with-main, and "just works".

using System;
using com.ericsson.otp.erlang;

public class ClientNode
{
 
 [STAThread]
 public static void  Main(System.String[] _args)
 {
  
  OtpSelf cNode = new OtpSelf("clientnode", "cookie");
  OtpPeer sNode = new OtpPeer("servernode@chloe.ravnaandtines.com");
  OtpConnection connection = cNode.connect(sNode);
  
  OtpErlangObject[] args = new OtpErlangObject[]{new OtpErlangLong(1), new OtpErlangLong(2)};
  connection.sendRPC("mathserver", "add", args);
  OtpErlangLong sum = (OtpErlangLong) connection.receiveRPC();
  if (sum.intValue() != 3)
  {
   throw new System.SystemException("Assertion failed, returned = " + sum.intValue());
  }
  System.Console.Out.WriteLine("OK!");
 }
}

How do we divest ourselves of the dependency on the J# runtime?

The Java Language Conversion Assistant generates C# code that doesn't compile, and would require a moderate amount of patching up for differences between superficially similar API classes. It is by no means a one evening exercise.

The OtpErlang.jar similarly fails to be converted by the jbImp tool, but IKVM converts it nicely; so at the penalty of having 27Mb worth of IKVM.OpenJDK.ClassLibrary.dll and IKVM.Runtime.dll instead of 3.7Mb of J#, driving the ~100kb dll, you can build the C# program and it passes this simple proof of concept.

IronPython and the DLR

Porting the code is easy enough

import clr
clr.AddReference("jinterface.dll")
from System import Array

from com.ericsson.otp.erlang import *

cNode = OtpSelf("clientnode", "cookie")
sNode = OtpPeer("servernode@chloe.ravnaandtines.com")
connection = cNode.connect(sNode);

args = Array[OtpErlangObject]([OtpErlangLong(1), OtpErlangLong(2)])
connection.sendRPC("mathserver", "add", args)
sum = connection.receiveRPC()
if (sum.intValue() != 3):
  print "Assertion failed, returned = " + sum.intValue()
else:
  print "OK!"

A direct conversion, the only slightly tricky bit being coercing the list to an Array.

This works fine when calling into the J# built library, as expected. IKVM needs the latest version -- before 0.36.0.11 (see comments), though, is a different story.

>\IronPython-2.0B1\ipy.exe Program.py
Traceback (most recent call last):
  File Snippets, line unknown, in Initialize
  File OtpErlang, line unknown, in connect
  File OtpErlang, line unknown, in .ctor
  File OtpErlang, line unknown, in .ctor
  File IKVM.OpenJDK.ClassLibrary, line unknown, in .ctor
  File IKVM.OpenJDK.ClassLibrary, line unknown, in init
  File IKVM.OpenJDK.ClassLibrary, line unknown, in getContext
  File IKVM.OpenJDK.ClassLibrary, line unknown, in getStackAccessControlContext
  File IKVM.Runtime, line unknown, in getStackAccessControlContext
  File IKVM.Runtime, line unknown, in GetProtectionDomainFromType
  File IKVM.Runtime, line unknown, in getProtectionDomain0
  File IKVM.Runtime, line unknown, in GetProtectionDomain
SystemError: The invoked member is not supported in a dynamic assembly.

Trace taken with 0.36.0.5, from last December. Fortunately, that wasn't some fundamental architectural thing being run into.

Integrating .Net with Erlang, part 1

So, you can integrate Java with Erlang, using the jinterface code supplied as part of the standard Erlang distribution. It's Java, so can we do this with .Net?

Let's start with J#. We set up the erlang source

-module(mathserver).
-export([start/0, add/2]).

start() ->
   Pid = spawn(fun() -> loop() end),
   register(mathserver, Pid).

loop() ->
   receive
      {From, {add, First, Second}} ->
        From ! {mathserver, First + Second},
        loop()
   end.

add(First, Second) ->
   mathserver ! {self(), {add, First, Second}},
   receive
      {mathserver, Reply} -> Reply
   end.

and test it with

>erl -name servernode@chloe.ravnaandtines.com -setcookie cookie
Eshell V5.6  (abort with ^G)
(servernode@chloe.ravnaandtines.com)1> node().
'servernode@chloe.ravnaandtines.com'
(servernode@chloe.ravnaandtines.com)2> pwd().
jinterface          mathserver.erl
ok
(servernode@chloe.ravnaandtines.com)3> c(mathserver).
l{ok,mathserver}
(servernode@chloe.ravnaandtines.com)4> s().
jinterface          mathserver.beam     mathserver.erl
ok
(servernode@chloe.ravnaandtines.com)5> mathserver:start().
true
(servernode@chloe.ravnaandtines.com)6> mathserver:add(1,2).
3
(servernode@chloe.ravnaandtines.com)7>

Now fire up Visual Studio, and create a J# class library project. Add existing items to copy the jinterface code into the project, and build it.

Cannot find type 'java.lang.ref.WeakReference'

Well, that's not too bad. Give it a stub, and build again:

package java.lang.ref;
import System.*;

public class WeakReference extends System.WeakReference
{
    public WeakReference()
    {
    }
}

gives us

error VJS1227: Cannot find constructor 'java.lang.ref.WeakReference(com.ericsson.otp.erlang.OtpMbox)'
error VJS1223: Cannot find method 'get()' in 'java.lang.ref.WeakReference'

So give it what it needs:

package java.lang.ref;
import System.*;

public class WeakReference extends System.WeakReference
{
    public WeakReference(Object o)
    {
        super(o);
    }
    public Object get()
    {
        try
        {
            return this.get_Target();
        }
        catch (InvalidOperationException ioe)
        {
            return null;
        }
    }
}

And it builds. Now put in the driver program:

import com.ericsson.otp.erlang.*;

public class ClientNode
{

    public static void main(String[] _args) throws Exception
    {

        OtpSelf cNode = new OtpSelf("clientnode", "cookie");
        OtpPeer sNode = new OtpPeer("servernode"+"@"+"chloe.ravnaandtines.com");
        OtpConnection connection = cNode.connect(sNode);

        OtpErlangObject[] args = new OtpErlangObject[]{
            new OtpErlangLong(1), new OtpErlangLong(2)};
        connection.sendRPC("mathserver", "add", args);
        OtpErlangLong sum = (OtpErlangLong)connection.receiveRPC();
        if (sum.intValue() != 3)
        {
                throw new IllegalStateException("Assertion failed, returned = " + sum.intValue());
        }
        System.out.println("OK!");

    }

}

Now, run that and get:

OK!

So, we have first light!

Get the goodies from here.