Home | Middleware News Brief Archive | OCI Education Services

# CORBA, Part II:

# .NET Development using IKVM, Topshelf and IIOP.NET

by
Charles Calkins, Principal Software Engineer
Object Computing, Inc. (OCI)

## 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.

### 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.

## 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:

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].

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>

      <configSections>
        <section name="log4net"
          type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
      </configSections>

      <log4net>
        <appender name="EventLogAppender"
          type="log4net.Appender.EventLogAppender">
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern
              value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/>
          </layout>
        </appender>
        <root>
          <level value="ALL"/>
          <appender-ref ref="EventLogAppender"/>
        </root>
      </log4net>

      <startup>
        <supportedRuntime version="v4.0.30319"/>
      </startup>

      <appSettings>
        <add key="NamingServerHost" value="ako"/>
        <add key="NamingServerPort" value="2809"/>
      </appSettings>

    </configuration>

Once `CSService.exe` is built, a number of command-line options are available from Topshelf for service management [11], including `install`, `uninstall`, `start` 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.

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:

## 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:

## 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.

That gives us:

## 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.dll`, `Math.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:

## 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

[1] CORBA, Part I: Java and the Web using the Play Framework
http://mnb.ociweb.com/mnb/MiddlewareNewsBrief-201301.html

[2] Play!
http://www.playframework.org/

[3] Please give system hostname IP address 127.0.1.1
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=316099

[4] IKVM.NET
http://www.ikvm.net/

[5] Common Intermediate Language
http://en.wikipedia.org/wiki/Common_Intermediate_Language

[6] IKVM.NET License
http://sourceforge.net/apps/mediawiki/ikvm/index.php?title=License

[7] GNU Classpath
http://www.gnu.org/software/classpath/license.html

[8] Topshelf
http://topshelf-project.com/

[9] log4net Tutorial
http://www.codeproject.com/Articles/140911/log4net-Tutorial

[10] Apache log4net Config Examples
http://logging.apache.org/log4net/release/config-examples.html

[11] Topshelf Command-Line Reference
http://docs.topshelf-project.com/en/latest/overview/commandline.html

[12] Mono
http://www.mono-project.com

[13] Mono FAQ: Licensing
http://www.mono-project.com/FAQ:_Licensing

Top