CORBA Part II: .NET Development Using IKVM, Topshelf, and IIOP.NET

Middleware News Brief (MNB) features news and technical information about Open Source middleware technologies.

INTRODUCTION

In part I of this article [1], we designed a simple CORBA-based client-server system in Java where clients use remote objects that implement the Adder interface to sum two numbers. The Naming Service was used to allow clients to locate objects that implemented desired functionality. Command-line-based client and server applications were shown, as well as a web-based client using the Play Framework [2], running under Linux.

This article builds upon the former by continuing development under Microsoft Windows, and showing that clients and servers on both platforms work together in a multi-platform, mixed-language, unified system.

WINDOWS AND JAVA

We continue with our Java example by adding a second architecture, represented by cko, a machine running 64-bit Windows 7, with Visual Studio 2010 and Java 1.7.0_09. JavaClient and JavaServer as developed in the previous article run identically as they did under Linux.

MPC logo

Sidebar

To build all projects under Windows, the NETCORBAWEB_ROOT environment variable must be set must be set to point to the top level of the extracted source, as well as the environment variable IKVM_ROOT, to point to the IKVM installation directory. Projects, included in the code archive that accompanies this article, are then generated by MPC with the vc10 project type as follows:

> $MPC_ROOT/mwc.pl -type vc10 NetCorbaWeb.mwc

and the solution built in Visual Studio.

This builds all of the projects, including JavaClient.jar and JavaServer.jar.

If a Debian-based system is used and a connection to the Naming Service fails, it may be due to a Debian bug [3], as was experienced by the author.

When referencing the Naming Service on ako from cko, an initial request would be made to the expected Naming Service URI of corbaloc:iiop:1.0@ako:2809/NameService, and while this connection was successful, a LocationForward response was received to iiop1.2://127.0.1.1:1049, which caused a subsequent connection failure, and the request as a whole to fail, as no Naming Service was executing on cko at that address.

Updating /etc/hosts on ako to provide a legitimate IP address for the entry for ako instead of 127.0.1.1, and then restarting orbd, corrected the problem and connections to the Naming Service from cko were successful.

We start the server, referencing the Naming Service on ako:

> java -jar JavaServer.jar -ORBInitialHost ako -ORBInitialPort 2809
Running Java_9132@cko

Running JavaClient.jar in the same manner shows that two Adder objects are now available to service the addition request.

> java -jar JavaClient.jar -ORBInitialHost ako -ORBInitialPort 2809 -add 3 17
Java_31313@ako: 20
Java_9132@cko: 20

The configuration of the above processes is shown in the following diagram, where clients are represented as diamonds, servers as ovals, and the Naming Service as a triangle. Here, and in successive diagrams, newly-started processes are highlighted in gray.

Figure 1. CORBA Naming Service Processes

Figure 1. CORBA Naming Service Processes

WINDOWS AND .NET WITH IKVM

IKVM [4] includes ikvmc, a translator from Java bytecode to the Common Intermediate Language (CIL) [5] of .NET. This allows an existing Java jar file to be used directly from a .NET application, providing access to large amounts of code already written in Java to be used in .NET applications. IKVM is provided under a BSD-style license [6], with incorporated OpenJDK and related code licensed under the GPL v2 plus Classpath exception [7].

Conversion of a Java library via ikvmc, for example, as with the Apache Commons CIL jar file, can be converted in this manner:

> %IKVM_ROOT%\bin\ikvmc commons-cli-1.2.jar

By default, this produces commons-cli-1.2.dll. Additional command-line arguments are available to specify the name of the output assembly when the default is not appropriate, to allow other assemblies to be referenced, and the like. The projects IDLToNet and JavaAddLibToNet in the sample workspace that accompanies this article integrate this conversion with MPC, so updated assemblies will be produced when the corresponding jar files are modified.

IKVM includes OpenJDK for Java support, so any included Java library that is part of OpenJDK is available for use in .NET, such as the CORBA functionality used in this article. While we could use Java's CORBA API directly from .NET, for convenience we will use the classes in JavaAddLib. Thus, a server application is as follows, which is nearly identical to that of JavaServer.java, save for a few small changes. For one, the package names used in Java have become .NET namespaces.

// CSServer/CSServer.cs
namespace CSServer
{
    using System;
    using System.IO;
 
    using com.ociweb.JavaAddLib;
    using org.apache.commons.cli;
    using System.Diagnostics;
 
    class CSServer
    {

The classes from the Apache Commons CLI project are used directly, and org.apache.commons.cli.ParseException is now a subclass of .NET's System.Exception, so can be handled as any other .NET exception.

        public static CommandLine ParseArgs(String[] args)
        {
            try
            {
                CommandLineParser parser = new PosixParser();
                Options options = new Options();
                options.addOption("?", "help", false, 
                    "print this message");
                options.addOption(null, "ORBInitialPort", true, 
                    "nameserver port (required)");
                options.addOption(null, "ORBInitialHost", true, 
                    "nameserver host");
                CommandLine line = parser.parse(options, args);
 
                if (line.hasOption("help") || 
                    !line.hasOption("ORBInitialPort"))
                {
                    HelpFormatter formatter = new HelpFormatter();
                    formatter.printHelp("Server", options);
                    return null;
                }
 
                return line;
            }
            catch (ParseException e)
            {
                Console.WriteLine(e.getMessage());
                return null;
            }
        }

The body of the server is the same as before, although the CTRL-C handler has been replaced with its .NET equivalent, and the instance name is constructed directly from the process ID and the machine name.

        static void Main(string[] args)
        {
            try
            {
                CommandLine line = ParseArgs(args);
                if (line == null)
                    return;
 
                String instanceName = "CS_" + 
                    Process.GetCurrentProcess().Id + "@" + 
                    Environment.MachineName;
                AddServer s = new AddServer();
 
                Console.CancelKeyPress +=
                    new ConsoleCancelEventHandler(
                        (object obj, ConsoleCancelEventArgs e) =>
                        {
                            e.Cancel = true;
                            Console.WriteLine("Shutting down...");
                            s.shutdown();
                        });
 
                s.init(args);
                Console.WriteLine("Running " + instanceName);
                s.run(instanceName);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }
}

The client is also nearly the same as JavaClient.java. Once again, ParseArgs() is not shown here, but is similar to what has been presented for the server.

// CSClient/CSClient.cs
namespace CSClient
{
    using System;
    using System.IO;
 
    using org.apache.commons.cli;
    using com.ociweb.JavaAddLib;
 
    class CSClient
    {
        public static CommandLine ParseArgs(String[] args)
        {
            ...
        }

The client is the same as before, although Java generics are converted to non-generic types in .NET. In particular, java.util.Vector is still used directly, although the contents must now be cast to type Ref when they are used.

        static void Main(string[] args)
        {
            CommandLine line = ParseArgs(args);
            if (line == null)
                return;
 
            String[] opt = line.getOptionValues("add");
            int x = Convert.ToInt32(opt[0]);
            int y = Convert.ToInt32(opt[1]);
 
            AddClient c = new AddClient();
            c.init(args);
 
            java.util.Vector refs = c.list();
            if (refs.size() == 0)
                Console.WriteLine("No servers found");
            else
            {
                java.util.Iterator it = refs.iterator();
                while (it.hasNext())
                {
                    Ref r = (Ref)it.next();
                    Console.Write(r.Name + ": ");
                    try
                    {
                        Console.WriteLine(c.add(r, x, y));
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }
            }
 
            c.destroy();
        }
    }
}

We can now start the server in one window, providing the host and port of the Naming Service that is running on the Linux host:

> CSServer -ORBInitialHost ako -ORBInitialPort 2809
Running CS_7336@CKO

and the client in another, and the output shows that the additional Adder object is used.

> CSClient -ORBInitialHost ako -ORBInitialPort 2809 -add 13 12
Java_31313@ako: 25
CS_7336@CKO: 25
Java_9132@cko: 25

This yields:

Figure 2.

Figure 2.

Rather than running as a user process, it can be useful to create a CORBA server as a Windows service. This allows it to run without the need of a user to be logged in, plus can take advantage of system reliability features, such as automatic service restart on failure. With the assistance of the Topshelf library [8], creating a Windows service to host the servant is straightforward. Topshelf is distributed under the Apache License version 2.0.

Two classes need to be defined. The first is the service itself, and the second is its installer. The service class begins by creating an instance of the AddServer class from JavaAddLib, and providing a reference to a .NET task object within which to execute it.

// CSService/CSService.cs
using System.Timers;
using System;
using Topshelf;
 
namespace CSService
{
    using System;
    using System.IO;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using com.ociweb.JavaAddLib;
    using log4net.Config;
    using System.Configuration;
 
    public class CSService
    {
        AddServer _s = new AddServer();
        Task _serviceTask = null;

The Start() method of the service class starts the execution of the service, and will be invoked automatically by the system when the service starts to run. This method is expected to terminate fairly quickly, else Windows generates an error indicating that the service did not start properly.

We'll use log4net for logging, so we obtain the log object.

        public void Start()
        {
            log4net.ILog log = 
                log4net.LogManager.GetLogger(typeof(Program));
            try
            {

As with the Play web application, we will obtain the location of the Naming Service from a configuration file — App.config in this case.

               String instanceName = "CSS_" + 
                    Process.GetCurrentProcess().Id + "@" + 
                    Environment.MachineName;
                string namingServerHost = 
                    ConfigurationManager.AppSettings["NamingServerHost"];
                int namingServerPort = 
                    Convert.ToInt32(ConfigurationManager.
                        AppSettings["NamingServerPort"]);
 
                log.Info("Starting " + instanceName + 
                    " with Naming Service at " + namingServerHost + 
                    ":" + namingServerPort);
                _s.init(namingServerPort, namingServerHost);

We complete the implementation of Start() by runing the ORB in a Task to allow Start() to terminate in a timely manner, yet still allow the service to continue to execute.

                _serviceTask = Task.Factory.StartNew(() =>
                {
                    try
                    {
                        _s.run(instanceName);
                    }
                    catch (Exception e)
                    {
                        log.Error("run() failed", e);
                    }
                }
                );  
            }
            catch (Exception e)
            {
                log.Error("Start() failed", e);
            }
        }

The Stop() method is called automatically by the system when the service is to be shut down. We stop the execution of the ORB, and wait at most 10 seconds for the .NET task to terminate.

        public void Stop()
        {
            if (_s != null)
                _s.shutdown();
            if (_serviceTask != null)
                _serviceTask.Wait(10 * 1000);
        }
    }

The installer for the service is implemented in Main(). To ensure that all log messages are reported, log4net is initialized with the call to XmlConfigurator.Configure() before any other code executes. This ensures that App.config is parsed, and log4net configured appropriately.

    public class Program
    {
        public static void Main()
        {
            XmlConfigurator.Configure();
            log4net.ILog log = 
                log4net.LogManager.GetLogger(typeof(Program));

An instance of the service class is instantiated as svc, and is identified as the service via the call to ConstructUsing(). The Start() and Stop() methods of the service class are mapped to the system events via WhenStarted() and WhenStopped().

            try
            {
                CSService svc = new CSService();
 
                HostFactory.New(x =>
                {
                    x.Service<CSService>(s =>
                    {
                        s.ConstructUsing(name => svc);
                        s.WhenStarted(t => t.Start());
                        s.WhenStopped(t => t.Stop());
                    });

Additional properties are set on the service, such as the Windows user account under which to execute (LocalSystem, in this case), to start automatically, and how it should be identified. Topshelf can use log4net itself for its messages, so UseLog4Net() is called to allow it to do that. log4net will be logging to the Windows event log, so DependsOnEventLog() ensures that the event logger service is running before this service requires it. Other dependencies can be created, such as to Microsoft SQL Server for services that need database access.

                    x.RunAsLocalSystem();
                    x.StartAutomatically();  
                    x.DependsOnEventLog();
                    x.UseLog4Net();
                    x.SetDescription("CSService service");
                    x.SetDisplayName("CSService");
                    x.SetServiceName("CSService");

Log messages are generated when the service is installed and uninstalled. If an event log is missing, logging a message will create it only if the process that logs the message has administrative rights. Since administrative rights are necessary to install services, logging a message at installation time both provides additional information to the user, and ensures that the event log has been created. The service itself, running as a non-administrative user, can then log messages at will.

                    x.AfterInstall(() => 
                        log.Info("CSService installed"));
                    x.AfterUninstall(() => 
                        log.Info("CSService uninstalled"));
                }).Run();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Lastly, we must configure log4net and provide the Naming Service connection parameters. This is done in the App.config file, as follows. For additional information in the use and configuration of log4net, such as how to log to files and databases, please see [9][10].

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3.  
  4. <configSections>
  5. <section name="log4net"
  6. type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"></section>
  7. </configSections>
  8.  
  9. <log4net>
  10. <appender name="EventLogAppender"
  11. type="log4net.Appender.EventLogAppender">
  12. <layout type="log4net.Layout.PatternLayout">
  13. <conversionPattern
  14. value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"></conversionPattern>
  15. </layout>
  16. </appender>
  17. <root>
  18. <level value="ALL"></level>
  19. <appender-ref ref="EventLogAppender"></appender>
  20. </root>
  21. </log4net>
  22.  
  23. <startup>
  24. <supportedRuntime version="v4.0.30319"></supportedRuntime>
  25. </startup>
  26.  
  27. <appSettings>
  28. <add key="NamingServerHost" value="ako"></add>
  29. <add key="NamingServerPort" value="2809"></add>
  30. </appSettings>
  31.  
  32. </configuration>

Once CSService.exe is built, a number of command-line options are available from Topshelf for service management [11], including installuninstallstart and stop. The following command installs the service, invoking administrative rights if the user does not currently hold them.

> CSService install --sudo

CSService now appears in the service list in the Services control panel applet. It can be started via the applet, or by this command:

> CSService start

The Windows Event Viewer confirms that the server was started.

Figure 3. Windows Event Viewer

Figure 3. Windows Event Viewer

Running a client shows that the service is among those contacted.

> CSClient -ORBInitialHost ako -ORBInitialPort 2809 -add 12 19
Java_31313@ako: 31
CS_7336@CKO: 31
CSS_8136@CKO: 31
Java_9132@cko: 31

This produces:

Figure 4. Services Contacted

Figure 4. Services Contacted

WINDOWS AND .NET WITH IIOP.NET

While using the Java ORB in .NET works successfully, and is especially convenient if existing Java code can be leveraged, having a solution that is entirely written in C# may be preferred. IIOP.NET, while not directly implementing the CORBA ORB API, does provide CORBA connectivity to .NET applications. IIOP.NET is licensed under the LGPL.

We first need Math.idl processed into an interface to implement. IIOP.NET provides an IDL compiler, IDLToCLSCompiler, which is invoked within the IDLIIOP project. The execution of the compiler produces Math.dll, which is then referenced by the other IIOP.NET-based applications.

As we did with JavaAddLib.jar, we create IIOPLib.dll so code can be shared between client and server applications. As before, we create class Ref to store object references, although here, rather than being of type org.omg.CORBA.Object, they are of type MarshalByRefObject. This represents a .NET object that communicates by exchanging messages via a proxy, rather than being copied in its entirety (marshalled by value).

// IIOPLib/Naming.cs
using System;
using System.Collections.Generic;
using omg.org.CosNaming;
using Ch.Elca.Iiop.Services;
using omg.org.CosNaming.NamingContext_package;
using System.Reflection;
 
namespace IIOPLib
{
    public class Ref
    {
        public string Name { get; private set; }
        public MarshalByRefObject Obj { get; private set; }
        public Ref(string name, MarshalByRefObject obj)
        {
            Name = name;
            Obj = obj;
        }
    }

Next, we wish to encaspulate interaction with the Naming Service as we did under Java. We create class Naming for this purpose, constructing it with Naming Service connection parameters.

    public class Naming
    {
        NameComponent[] _adders = 
            new NameComponent[] { new NameComponent("Adders", "") };
        string _orbInitialHost;
        int _orbInitialPort;
 
        public Naming(string orbInitialHost, int orbInitialPort)
        {
            _orbInitialHost = orbInitialHost;
            _orbInitialPort = orbInitialPort;
        }

As before, the Bind() operation binds the reference with its name in the Naming Service. While the code is similar to what was written in Java, there are two main differences. The first is that an object of type CorbaInit is used, rather than one of type org.omg.CORBA.ORB. While IIOP.NET does provide CORBA functionality, its API differs. The second is that, while an AlreadyBound exception is thrown when the Adders context already exists, it can't be caught directly, at least with IIOP.NET version 1.9.3, which is current as of the writing of this article. The exception thrown is a TargetInvocationException, where the exception that caused it to be thrown, available via the InnerException property, is the AlreadyBound exception.

        public void Bind(CorbaInit init, Ref r)
        {
            NamingContext nsCtx = 
                init.GetNameService(_orbInitialHost, _orbInitialPort);
 
            NamingContext addersCtx = null;
            try
            {
                addersCtx = nsCtx.bind_new_context(_adders);
            }
            catch (TargetInvocationException e)
            {
                // ok - context already exists
                if (e.InnerException is AlreadyBound)
                    addersCtx = (NamingContext)nsCtx.resolve(_adders);
                else
                    throw e;
            }
 
            NameComponent[] name = { new NameComponent(r.Name, "") };
            addersCtx.rebind(name, r.Obj);
        }

The Unbind() method removes the reference, as well as the Adders context if it is empty.

        public void Unbind(CorbaInit init, Ref r)
        {
            NamingContext nsCtx = 
                init.GetNameService(_orbInitialHost, _orbInitialPort);
            NamingContext addersCtx = 
                (NamingContext)nsCtx.resolve(_adders);
 
            NameComponent[] name = { new NameComponent(r.Name, "") };
            addersCtx.unbind(name);
 
            // if the adder that was just removed was the last one
            // remove the context, too
            if (List(init).Count == 0)
            {
                addersCtx.destroy();
                nsCtx.unbind(_adders);
            }
        }

Once again, the List() operation iterates over the bindings found in the Adders context, and returns them to the user. As before, while the NotFound exception is thrown, it is not directly available to be caught.

        public List<Ref> List(CorbaInit init)
        {
            List<Ref> refList = new List<Ref>();
 
            NamingContext nsCtx = 
                init.GetNameService(_orbInitialHost, _orbInitialPort);
            NamingContext addersCtx = null;
            try
            {
                addersCtx = (NamingContext)nsCtx.resolve(_adders);
            }
            catch (TargetInvocationException e)
            {
                if (e.InnerException is NotFound)
                    return refList;  // no context, so no adders
                else
                    throw e;
            }
 
            Binding[] bindings = null;
            BindingIterator bi = null;
            int chunkSize = 100;
            addersCtx.list(chunkSize, out bindings, out bi);
            AddBindings(bindings, addersCtx, refList);
            if (bi != null)
            {
                while (bi.next_n(chunkSize, out bindings))
                    AddBindings(bindings, addersCtx, refList);
                bi.destroy();
            }
 
            return refList;
        }
 
        void AddBindings(Binding[] bindings, NamingContext addersCtx, 
            List<Ref> refList)
        {
            for (int i = 0; i < bindings.Length; i++)
            {
                MarshalByRefObject objRef = 
                    addersCtx.resolve(bindings[i].binding_name);
                String name = bindings[i].binding_name[
                    bindings[i].binding_name.Length - 1].id;      	
                refList.Add(new Ref(name, objRef));
            }
        }
    }
}

This completes IIOPLib. We can now implement the server. The IIOP.NET version of a servant inherits from both the IDL-generated interface, as well as MarshalByRefObject. .NET maintains a distributed garbage collector, freeing server objects when their lifetime has expired. By overriding InitializeLifetimeService() to return null instead of an object of type ILease with a finite lifespan, we ensure that the object is not garbage collected until the service as a whole shuts down.

// CSServerIIOP/CSServerIIOP.cs
using System;
using System.Runtime.Remoting.Channels;
using System.Threading;
using Ch.Elca.Iiop;
using Ch.Elca.Iiop.Services;
using System.Diagnostics;
using System.Runtime.Remoting;
using IIOPLib;
 
public class MathImpl : MarshalByRefObject, MathModule.Math
{
    public override object InitializeLifetimeService()
    {
        return null;  // live forever
    }
 
    public int Add(int x, int y)
    {
        return x + y;
    }
}

The server processes command-line arguments (not shown here) which set the Naming Service connection parameters.

public class CSServerIIOP
{
    static int _orbInitialPort = 0;
    static string _orbInitialHost = "localhost";
 
    public static bool ParseArgs(string[] args)
    {
        ...
    }
 
    public static void Main(string[] args)
    {
        try
        {
            if (!ParseArgs(args))
                return;

The remainder of the server is similar to that of JavaServer.java, except for minor differences. Again, no ORB is available, but CorbaInit.GetInit() acts as a substitute. A communications channel must be initialized and registered for use by IIOP.NET. A specific listening port for the channel can be specified, while 0 allows the system to select a port of its own choosing.

            CorbaInit init = CorbaInit.GetInit();
 
            // initialize the channel
            IiopChannel channel = new IiopChannel(0);
            ChannelServices.RegisterChannel(channel, false);

Rather than activating a POA and registering the servant with it, the servant is registered with the .NET remoting services via RemotingServices.Marshal().

            // create the Math servant
            MathImpl math = new MathImpl();
            string objectURI = "math";
            RemotingServices.Marshal(math, objectURI);

The remainder of the server is similar to what we have seen before, although the server blocks on a semaphore, rather than executing orb.run(), to allow the server to remain active to process Add() requests.

            Naming naming = 
                new Naming(_orbInitialHost, _orbInitialPort);
            String instanceName = "CSI_" + 
                Process.GetCurrentProcess().Id + "@" + 
                Environment.MachineName;
            Ref r = new Ref(instanceName, math);
            naming.Bind(init, r);
 
            Semaphore s = new Semaphore(0, 1);
            Console.CancelKeyPress +=
                new ConsoleCancelEventHandler(
                    (object obj, ConsoleCancelEventArgs e) =>
                    {
                        e.Cancel = true;
                        Console.WriteLine("Shutting down...");
                        s.Release();
                    });
 
            Console.WriteLine("Running " + instanceName);
            s.WaitOne();
 
            naming.Unbind(init, r);
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception: " + e);
        }
    }
}

The client parallels the server. It processes command-line arguments, registers a channel (though a client channel, this time), and invokes all of the Add() methods that are available.

using System;
using System.Runtime.Remoting.Channels;
using Ch.Elca.Iiop;
using IIOPLib;
using Ch.Elca.Iiop.Services;
using System.Collections.Generic;
 
public class CSClientIIOP {
    static int _x = 0;
    static int _y = 0;
    static int _orbInitialPort = 0;
    static string _orbInitialHost = "localhost";
 
    public static bool ParseArgs(string[] args) {
        ...
    }
 
    public static void Main(string[] args) {
        try {
            if (!ParseArgs(args))
                return;
 
            // initialize the channel
            IiopClientChannel channel = new IiopClientChannel();
            ChannelServices.RegisterChannel(channel, false); 
 
            // obtain the adder references from the naming service
            // and call Add() on each
            CorbaInit init = CorbaInit.GetInit();
            Naming naming = 
                new Naming(_orbInitialHost, _orbInitialPort);
            List<Ref> refs = naming.List(init);
            foreach (Ref r in refs)
            {
                MathModule.Math mathRef = (MathModule.Math)r.Obj;
                Console.Write(r.Name + ": ");
                try
                {
                    Console.WriteLine(mathRef.Add(_x, _y));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }
        } catch (Exception e) {
            Console.WriteLine("Exception: " + e);
        }
    }
}

The server and client are run as follows.

> CSServerIIOP -ORBInitialHost ako -ORBInitialPort 2809
Marshalling using for type: False
Running CSI_6644@CKO

and

> CSClientIIOP -ORBInitialHost ako -ORBInitialPort 2809 -add 12 18
Java_31313@ako: 30
CS_7336@CKO: 30
CSS_8136@CKO: 30
Java_9132@cko: 30
CSI_6644@CKO: 30

This gives:

Figure 5. cko_CSClientIIOP

Figure 5. cko_CSClientIIOP

WINDOWS AND THE WEB

The same Play application runs without modification under Windows, save a change to application.conf to point to the Naming Service on ako. Executing

$ play run

from the JavaWeb directory, and visiting http://localhost:9000 from a browser running on cko, shows the same application as before, but with the Play server hosted on Windows. This is demonstrated in the following screenshots.

Figure 6. Play Server Windows Screenshot

Figure 6. Play Server Windows Screenshot

Figure 7. Windows Screenshot CKO2

Figure 7. Windows Screenshot CKO2

That gives us:

Figure 8. Play Server

Figure 8. Play Server

LINUX AND .NET

Returning to ako, Mono [12] is a cross-platform .NET development framework, and recent versions support .NET 4. Various components of Mono are released under different licenses[13]. IIOP.NET 1.9.3, as well as the IIOP.NET-related projects in this article, compile and run successfully under Mono. When projects are generated by MPC with the make type, MPC invokes gmcs, the Mono C# compiler, on C# files. Thus, running make on the generated Makefiles builds the C# code that was authored under Windows.

After copying IIOPLib.dllMath.dll, and IIOPChannel.dll into the application directories, running this command at one prompt while in the CSServerIIOP directory:

$ ./CSServerIIOP.exe -ORBInitialPort 2809
Marshalling using for type: False
Running CSI_31830@ako

and this command at a second prompt while in the CSClientIIOP directory, shows that all of the servers are contacted.

$ ./CSClientIIOP.exe -ORBInitialPort 2809 -add 3 2
Java_31313@ako: 5
CS_7336@CKO: 5
CSS_8136@CKO: 5
Java_9132@cko: 5
CSI_31830@ako: 5
CSI_6644@CKO: 5

We now have:

Figure 9. Final Visualization

Figure 9. Final Visualization

CONCLUSION

This article has shown that CORBA is a viable technology for distributed, multi-platform, mixed-language systems. It is a proven, mature technology, which has already solved many of the problems that newer communications frameworks are now addressing. While CORBA provides advanced features to manage the remote invocation, a distributed system can still be created simply.

References