Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

Mixing ACE/TAO and .NET Clients and Servers

Rate me:
Please Sign up or sign in to vote.
4.65/5 (28 votes)
13 Dec 2005CPOL18 min read 83.6K   1.9K   45   13
Demonstrates mixing C++ ACE/TAO clients and servers with C# IIOP.NET clients and servers on Windows and Linux.

Figure 1 - Naming Service, Servers, and Clients in the Linux ACE/TAO C++ Domain and the Windows XP IIOP.NET C# Domain.

Figure 1 - Naming Service, Servers, and Clients in the Linux ACE/TAO C++ Domain and the Windows XP IIOP.NET C# Domain. A Naming Service can exist in either domains. This article will demonstrate the three examples shown.

Introduction

In theory, there will be some programmers who are fortunate enough to work entirely within the .NET environment and who are able to use the excellent .NET Remoting framework to accomplish all of their distributed computing tasks. In practice, however, the reality is that most programmers are required to work occasionally with collections of legacy code that predates the .NET framework (and sometimes predates the .NET programmers!), or current code that - for one reason or another - continues to be produced in alternate languages or frameworks. Inevitably, it seems that real-life programmers will find that they need to have a way to exchange data with non .NET clients and servers across heterogeneous networks and languages.

Clearly, this is a huge and complex topic, and many excellent articles and tutorials have already been written on the subject. However, one specific area that seems to me to have been neglected - at least, it seemed that way when I was looking for guidance myself - is a tutorial on how to provide interoperability between ACE/TAO Servers and Clients written in C++ and often running on Linux, and .NET Servers and Clients written in C# and almost always running on Windows.

This article will demonstrate the coding techniques that I have been able to use to successfully achieve cross-platform (Windows XP, Linux FC4), cross-language (C#, C++) client-server communication using the "ACE ORB" (TAO) and IIOP.NET.

This tutorial consists of three examples:

  • Example 1 - IIOP.NET C# Server, simultaneously communicating directly with an IIOP.NET C# Client and a TAO C++ Client. This example is relevant to the case where you want to use a C++ Client on Linux (or Windows) to communicate with a C# Server on Windows (or Linux), and you do not want to use - or do not have - a Naming Service.
  • Example 2 - TAO C++ Server, simultaneously communicating through a Naming Service with an IIOP.NET C# Client and a TAO C++ Client. This example is relevant to the case where you want to use an existing C++ TAO Server on Linux (or Windows) to communicate with a C# Client on Windows (or Linux), and you need to use a Naming Service running on Linux (or Windows). This will allow you to replace existing C++ Clients with C# clients in a way that is transparent to existing servers.
  • Example 3 - IIOP.NET C# Server, simultaneously communicating through a Naming Service with a TAO C++ Client and an IIOP.NET C# Client. This example is relevant to the case where you want to transparently replace an existing C++ TAO Server with a C# Server.

Background

If you are reading this tutorial, chances are that you are already familiar with either ACE/TAO or .NET, but perhaps not with both. Feel free to skip the parts that you already know.

.NET Remoting comes, out of the box, with binary and SOAP channels for the TCP and HTTP protocols. However, to satisfy the requirement to communicate with the Common Object Request Broker Architecture (CORBA), a channel for the IIOP protocol was developed by Dominic Ullmann and Patrik Reali. Their project, which is known as IIOP.NET, provides compatibility with a number of Object Request Brokers (ORBs), including TAO. So far, the major emphasis of the IIOP.NET project has been to support the IIOP protocol between Java and .NET. However, IIOP.NET can also support interoperability with C++ clients and servers through TAO, as this tutorial will show.

What You Need to Have Installed to Compile and Run the Examples

This tutorial will demonstrate a set of relatively simple examples using some relatively complex software. There are some pieces of software that you must have properly installed before the example code will compile and work for you:

ACE/TAO, in particular, is an intimidating install, and after being compiled (a multi-day process on a good computer), it weighs in at over 40,000 files and 2GB (and yes, that is not a misprint). A word of warning: there are some version variations in the directory structure of ACE/TAO, so you might find that you need to modify some paths to find all of the included header files and utilities referenced in the examples. Bring a lunch, and "Good Luck"! Of course, true aficionados will want to put ACE/TAO and IIOP.NET on both their Windows and Linux machines, and run the examples in both directions. Just because you can.

If you simply want to see the examples running, you can try the binary files (downloadable from the top of this tutorial). For Windows, I recommend modifying and using the included batch files Example1.bat, Example2.bat, and Example3.bat, which will walk you through the sequence. However, please be aware that Examples 2 and 3 expect you to have a TAO Naming Service available on the target machine - which means that you need to have ACE/TAO installed there.

In my setup, my Windows machine is slb001, and my Linux machine is slb002. When you read the code or run the examples, you should make the appropriate substitutions for your own environment.

ACE/TAO

The ACE ORB (TAO) is an open source implementation of CORBA. It is based on the standard Object Management Group (OMG) CORBA reference model, and is constructed using software concepts and frameworks provided by the Adaptive Communications Environment (ACE). It is a middleware technology that automates common network programming tasks including:

  • registration, location, and activation;
  • object request demultiplexing;
  • framing and error-handling;
  • parameter marshalling and demarshalling; and
  • operation dispatching.

The implementation languages for ACE/TAO are C and C++. The process that is used to produce servers and clients in ACE/TAO is beyond the scope of this tutorial, but a few concepts are worth reviewing:

  • Interfaces are defined in OMG standard .idl files. These .idl files are used by TAO to automatically generate C++ stubs and skeletons for the servers and clients using the tao_idl utility.
  • The programmer implements the interfaces in the generated I.cpp stub, and then uses the usual CORBA techniques to write the client and server code to use the implementation.
  • ACE/TAO provides a make utility that manages the project creation and compilation across platforms. A .mpc file, written by the programmer, defines the projects to be created and the source files to be used, and a Perl script creates the make files or the Visual Studio solution. On Linux, to produce GC++ projects, the command is: mwc.pl -type gnuace. On Windows, to produce Visual Studio .NET 2003 solutions, the command is: mwc.pl -type VC71.

IIOP.NET

IIOP.NET is an open source (LGPL) project that leverages the .NET Remoting framework by providing a custom channel that supports the IIOP protocol, enabling nearly transparent and seamless communication between distributed CORBA objects. An objective of IIOP.NET is to allow existing CORBA servers to be used without modification, and this objective has been achieved for TAO. Not only will IIOP.NET allow you to use legacy TAO servers without modifying them, but it will also allow you to transparently replace legacy or unmanaged C++ TAO Servers and Clients with managed C# equivalents.

Naturally, the implementation language for IIOP.NET is C#. IIOP.NET provides an IiopChannel that is analogous to the TcpChannel and the HttpChannel in the System.Runtime.Remoting namespace, and the programming methodology to create IIOP clients and servers is identical to the methodology normally used for these other channels.

The Naming Service

The trickiest part of getting the client-server communication to work properly in TAO/CORBA systems is dealing with the issues associated with navigating the naming graph - a hierarchy of naming contexts and name bindings. To succeed in this, we need to know something about the OMG Naming Service.

The OMG Naming Service is a lot like the Internet Domain Name Service (DNS), and both of these "Name Services" are a lot like the familiar White Pages telephone directory:

  • Telephone Directory: "Subscriber Names (John Smith)" map to "Telephone Numbers (555-1212)"
  • DNS: "Domain Names (www.mine.com)" map to "IP addresses (123.123.123.123)"
  • Naming Service: "Meaningful Names (IAdder)" map to "Object References (SLB.ExampleInterfaces.IAdder)"

The process of "mapping to" is what is meant by name binding. Every name maps to exactly one reference, but the same reference can be mapped to many names - although it is usually a good practice to avoid doing this since it doesn't provide any real value-addition and can cause unnecessary confusion. When you need an object to hold name bindings, you use a naming context, which in .NET terms is conceptually like a namespace, even though it is implemented more like a table. And, like namespaces (or file systems), naming contexts can be nested. The resulting tree of naming context branches and name binding leaves is the name graph. Managing entries and access to the name graph is the job of the Naming Service.

TAO Servers register themselves - and their object references - with the TAO Naming Service by binding contexts and objects below the top-level naming context, which is usually called "NameService". TAO clients locate references to the objects that they want to use by first connecting to the NameService naming context, and then working their way down the hierarchy of name-bound naming contexts until they come to the name-bound object they are looking for. Clearly, this scheme will only work when both the server and the client agree about where in the name graph the desired object can be found. The TAO distribution comes with a utility, nslist.exe, that can be used to inspect the name graph of a given Naming Service. A variation of this utility, nslister.exe, is included with the code downloads for this article. (It provides output that is more consistent with the examples, and uses terminology that is more familiar to .NET programmers.) An example of the output from the nslister utility is the following:

Figure 2 - Example Output of the nslister.exe Utility.

Figure 2 - Example output of the nslister.exe utility showing that something - we don't really know exactly what - is bound to the "ExampleInterfaces" naming context and the "IAdder" name. (We can only hope that it really is an object that actually implements the SLB.ExampleInterfaces.IAdder interface...).

In .NET Remoting, servers and clients connect directly with each other, and a Naming Service is not required. (Don't confuse the .NET services provided by the combination of the Universal Description, Discovery, and Integration (UDDI), the Discovery Protocol (DISCO), and the Web Service Description Language (WSDL) for a Naming Service - they are entirely different technologies for achieving somewhat the same result, and .NET Remoting does not actually depend upon any of them.)

There are three practical consequences that arise from this discussion of Naming Services:

  1. A TAO client - which would normally use a Naming Service - can bypass the Naming Service to communicate directly with an IIOP.NET Server. Of course, an IIOP.NET client can also do this using normal .NET programming techniques. (See Example 1.)
  2. An IIOP.NET client needs to use the TAO Naming Service in order to communicate with a TAO Server. (See Example 2.)
  3. If we want to use the same client (TAO or IIOP.NET) to communicate with either a TAO or IIOP.NET server, then the client will need to do so through the TAO Naming Service. (See Examples 2 and 3.)

The C# Interface, the IDL File, and the Implementations of Both

The C# Interface and Implementation

For these examples, we are going to define a very simple example interface, IAdder, which we will compile into a convenient library of example interfaces that we will call ExampleInterfaces.dll and which we will reference for our C# clients and servers.

C#
/// IAdder.cs
/// ...
using System;

namespace SLB.ExampleInterfaces
{
    /// <summary>The IAdder interface accepts two doubles
    /// and returns a double.</summary>
    public interface IAdder
    {
        double add(double arg1, double arg2);
    }
}

The interface implementation that will be "served" by the server is written in the same language and for the same platform as the server itself. For the IIOP.NET server - and since it is a very simple interface - it is convenient to put the implementation code into the same file as the server itself. In general, for more complex projects, the implementations should be in their own source files, and combined into a library which the server code will reference. In any event, our implementation looks like this:

C#
namespace SLB.ExampleServers
{
    /// <summary>
    /// The adder implementation class.
    /// </summary>
    public class Adder: MarshalByRefObject, IAdder 
    {
        public override object InitializeLifetimeService() 
        {
            // live forever
            return null;
        }

        public double add(double arg1, double arg2) 
        {
            return arg1 + arg2;
        }
    }

Since it is a remotable object, it needs to be derived from the MarshalByRefObject class, and of course it also needs to inherit the interface. The InitializeLifetimeService() method is overridden to ensure that the Adder object will not timeout and be garbage collected, and will continue to exist on the server until the server itself is destroyed, even if no client ever calls it.

The C++ Interface and Implementation

For the C++ programs, we need to express this same example interface in an OMG Interface Definition Language (.idl) file, which we will call ExampleInterfaces.idl.

#pragma prefix "SLB"

module ExampleInterfaces
{
    // The adder interface
    interface IAdder
    {
        double add(in double arg1, in double arg2);
    };
};

This .idl file can then be used to generate the necessary stubs and skeletons for the TAO implementation using the command: tao_idl -GI ExampleInterfaces.idl. The following files are generated:

  • For the Client: ExampleInterfacesC.cpp, ExampleInterfacesC.h, ExampleInterfacesC.inl.
  • For the Server: ExampleInterfacesS.cpp, ExampleInterfacesS.h, ExampleInterfacesS.inl, ExampleInterfacesS_T.cpp, ExampleInterfacesS_T.h, ExampleInterfacesS_T.inl.
  • For the Interface Implementation (because we used the -GI option): ExampleInterfacesI.cpp, ExampleInterfacesI.h.

For the most part, these generated files contain an indecipherable complexity of code not meant for mortal programmers, and it is best to simply avert one's gaze and give thanks for the courageous coders who have gone before us, selflessly enduring the horrors associated with making such a brutal thing "just work" on our behalf.

One of the first things that we should do with these files is to rename the I.cpp and I.h files so that they do not get overwritten if tao_idl is ever again run against the same target .idl file. The convention is to rename them to _i.cpp and _i.h, and one ought to follow this convention because some of the useful downstream utilities (such as the Make Project Creator and Make Workspace Creator) expect it.

The one file that we do need to modify (in addition to changing references to the I.h header file here and there as necessary) is the interface implementation file ExampleInterfaces_i.cpp. Insert the implementation code for the constructors and methods as necessary, in the places indicated in the generated code. Our implementation of the add() method looks like this:

CORBA::Double ExampleInterfaces_IAdder_i::add (
   ::CORBA::Double arg1,
   ::CORBA::Double arg2
  )
  ACE_THROW_SPEC ((
    CORBA::SystemException
  ))
{
  // Insert the Implementation Here
  return (arg1 + arg2); // The implementation.
}

Using the IDLToCLSCompiler and the CLSToIDLGenerator

The IAdder interface is so simple that it was pretty easy to produce the .idl and the C# code for the dynamic link library manually. But what if the project was more realistic, and the interface was a lot more complex? What if we needed to work with an existing .idl file with dozens (and dozens) of modules, structures, interfaces, and who knows what?

In these cases, the solution to programmer fatigue and tedious errors is to use the purpose-built tools that IIOP.NET provides!

If you have written or been given an .idl file, and you need an equivalent .NET .dll to reference in your C# code, then you can use the IDLToCLSCompiler utility to produce the dynamic library automatically. For example, to generate the ExampleInterfaces.dll file from the ExampleInterfaces.idl file, use the following command:

IDLToCLSCompiler.exe ExampleInterfaces ExampleInterfaces.idl

On the other hand, if you already have a .NET library that you want to produce an .idl file from, then you can use the CLSToIDLGenerator utility. For example:

CLSToIDLGenerator.exe SLB.ExampleInterfaces.IAdder ExampleInterfaces.dll

will automatically produce the following .idl file:

// auto-generated IDL file by CLS to IDL mapper

// SLB\ExampleInterfaces\IAdder.idl

#include "orb.idl"
#include "Predef.idl"

#include "Ch\Elca\Iiop\GenericUserException.idl"
#ifndef __SLB_ExampleInterfaces_IAdder__
#define __SLB_ExampleInterfaces_IAdder__
module SLB {
module ExampleInterfaces {

abstract interface IAdder {
double add(in double arg1, in double arg2) raises 
          (::Ch::Elca::Iiop::GenericUserException);
};

#pragma ID IAdder "IDL:SLB/ExampleInterfaces/IAdder:1.0"

};
};

#endif

The similarities of this file with the manually produced .idl above are obvious, but the differences (which initially look a little alarming) ultimately don't matter. These are powerful utilities; read the documentation, and don't be afraid to use them.

Example 1 - IIOP.NET Server and Client on Windows, TAO Client on Linux

The C# IIOP.NET Server and the C# IIOP.NET Client

This example uses an IIOP.NET server, written in C# to run on Windows. Here is some of the relevant code:

C#
/// DotNetAdderServer.cs
/// ...

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Threading;

// ..\IIOPChannel.dll
using Ch.Elca.Iiop;

// from project ExampleInterfaces.dll
using SLB.ExampleInterfaces;


namespace SLB.ExampleServers
{
    /// <summary>
    /// This .Net Server class object publishes
    /// an SLB.ExampleInterfaces.IAdder implementation
    /// using the iiop protocol.
    /// </summary>
    public class DotNetAdderServer 
    {
        private static string host = "localhost";    //default host
        private static int port = 12345;             //default port

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        public static void Main(string[] args) 
        {
            try
            {
                ParseCommandLineArguments(args);

                // register the channel
                IiopChannel chan = new IiopChannel(port);
                ChannelServices.RegisterChannel(chan);

                Adder adder = new Adder();
                // the adder object is used,
                // but only the IAdder interface is seen...
                string objectURI = "IAdder";
                RemotingServices.Marshal(adder, objectURI);

                Console.WriteLine(".Net IAdder Server is Running...");
                Console.WriteLine("Clients can connect using iiop:"
                     + host + ":" + port.ToString() + "/" + objectURI);
                Thread.Sleep(Timeout.Infinite);
            }
            catch (Exception e)
            {
                ///...
            }
        }
            ///...
        }
    }
}

As you can see, the server code is exactly what you would expect it to be for .NET Remoting, except that instead of creating and registering a System.Runtime.Remoting.Channels.TcpChannel or a System.Runtime.Remoting.Channels.HttpChannel, it creates and registers a Ch.Elca.Iiop.IiopChannel from the IIOP.NET classes.

Similarly, the .NET client is exactly what you would expect, except that it also uses the Ch.Elca.Iiop.IiopChannel:

C#
/// DotNetAdderClient.cs
///...

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

// ..\IIOPChannel.dll
using Ch.Elca.Iiop;
using Ch.Elca.Iiop.Services;

// from project ExampleInterfaces.dll
using SLB.ExampleInterfaces;

namespace SLB.ExampleClients
{
    /// <summary>
    /// This client accepts two numbers from
    /// the user at the console, and then
    /// calls a remote object that exposes
    /// the IAdder interface in order to add them and
    /// return the result.
    /// 
    /// Usage is: DotNetAdderClient.exe host port
    /// </summary>
    public class DotNetAdderClient 
    {
        private static string host = "localhost";    //default host
        private static int port = 12345;             //default port

        /// <summary>
        /// The entry point for the application.
        /// </summary>
        /// <param name="args">Command line arguments: host port</param>
        [STAThread]
        public static void Main(string[] args) 
        {
            try 
            {
                ParseCommandLineArguments(args);

                // Register the Channel...
                //assign port automatically
                IiopClientChannel channel = new IiopClientChannel();
                ChannelServices.RegisterChannel(channel);

                // Get the reference to the servant object
                // using the interface...
                IAdder adder = 
                       (IAdder)RemotingServices.Connect(typeof(IAdder), 
                       "iiop://" + host + ":" + port.ToString() + 
                       "/IAdder");
                
                // Use the servant...
                InteractWithUser(adder);

            } 
            catch (Exception e)
            {
                ///...
            }
        }
        ///...
    }
}

The C++ TAO Client for the IIOP.NET Server

The C++ TAO client bypasses the naming service to connect directly to the IIOP.NET Server. In keeping with TAO/CORBA conventions, the client is started with a command line argument that identifies an ORBInitRef. In this case, the command line arguments are: -ORBInitRef IAdder=iiop://localhost:12345/IAdder. Some of the relevant TAO client code is presented below:

// DirectTAOAdderClient.cpp
//
// This example demonstrates a direct connection
// between a C# .NET IIOPNet based server and a C++
// TAO based client, communicating using
// the iiop protocol, and not using a NameService.
// ...
#include "ExampleInterfacesC.h"
#include <acestreams.h><ace/streams.h>

int main( int argc, char *argv[] )
{
  try 
  {
    // Initialize the CORBA Object Request Broker.
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv );

    // Instead of using the TAO NameService,
    // resolve the desired interface directly, based
    // upon the -ORBInitRef passed in on the
    // command line and used to initialize the ORB.
    // For example: -ORBInitRef IAdder=iiop://localhost:12345/IAdder
    CORBA::Object_var obj = orb->resolve_initial_references("IAdder");

    // Narrow to the interface of interest,
    // and make sure it is the type we expect it to be.
    ExampleInterfaces::IAdder_var iAdder = 
           ExampleInterfaces::IAdder::_narrow(obj.in());
    if (CORBA::is_nil(iAdder.in())) 
    {
      cerr << "Could not narrow to an IAdder reference" << endl;
      return 1;
    }

    // Now use the remote object...
    cout << "Using a remote object that " 
            "implements the IAdder interface..." << endl;
    cout << endl;
    double number1 = 0;
    double number2 = 0;
    double sum = 0;
    while (true)
    {
        cout << "Enter the first number: ";
        cin >> number1;
        cout << "Enter the second number: ";
        cin >> number2;
        // the remote object is used here...
        sum = iAdder->add(number1, number2);
        cout << "The sum is: " << sum << endl;
        cout << "------------------" << endl;
    }
  }
  catch ( CORBA::Exception& ex ) 
  {
    cerr << "Caught a CORBA::Exception: " << ex << endl;
    return 1;
  } 
  return 0;
}

You can run this example (all on Windows) using the Example1.bat file provided with the binaries download. To run this example manually, do the following:

  1. Start the IIOP.NET Server on the Windows machine: DotNetAdderServer.exe slb001 12345.
  2. Start the IIOP.NET Client on the Windows machine: DotNetAdderClient.exe slb001 12345.
  3. Start the TAO Client on the Linux machine: ./TAODirectAdderClient -ORBInitRef IAdder=iiop://slb001:12345/IAdder.

Example 2 - TAO Server, TAO Client, TAO Naming Service on Linux, IIOP.NET Client on Windows

The C++ ACE/TAO Linux Server and Client

For this example, we will use a TAO Server and TAO Client, both written in C++ and running on Linux. The server code looks like this (note the comments in the code):

// TAOAdderServer.cpp
//
// This is a simple NameService using server implementation using ACE/TAO.
//
// This example serves an SLB.ExampleInterfaces.IAdder object.
// The IAdder object is defined 
// in ExampleInterfaces.idl and implemented in ExampleInterfaces_i.cpp.
//...

#include "ExampleInterfaces_i.h"
#include <orbsvcs/CosNamingC.h>
#include <orbsvcs/Naming/Naming_Client.h>
#include <ace/streams.h>

int main( int argc, char *argv[] )
{
  try 
  {
    // Initialize the CORBA Object Request Broker.
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv );

    //Get reference to the Root POA
    CORBA::Object_var obj = 
           orb->resolve_initial_references( "RootPOA" );
    PortableServer::POA_var poa = 
           PortableServer::POA::_narrow( obj.in() );

    // Activate the POA Manager
    PortableServer::POAManager_var mgr = poa->the_POAManager();
    mgr->activate();

    // Find the CORBA Services Naming Service
    CORBA::Object_var naming_obj = 
           orb->resolve_initial_references("NameService");
    CosNaming::NamingContext_var root = 
               CosNaming::NamingContext::_narrow(naming_obj.in());
    if(CORBA::is_nil(root.in()))
    {
      cerr << "Could not narrow NameService to NamingContext!" << endl;
      throw 0;
    }

    // Bind the Naming Context to a well known
    // name so clients can find it by name.
    // Interested parties can see the "well known"
    // namespace's and interfaces that might 
    // be available on a given NameService using
    // a utility like nslist or nslister.
    // It makes sense to use the IDL-defined Module
    // (namespace) for the first entry...
    CosNaming::Name name;
    name.length(1);
    // Namespace "SLB.ExampleInterfaces"
    name[0].id = CORBA::string_dup("ExampleInterfaces");
    
    try 
    {
      CORBA::Object_var dummy = root->resolve(name);
    }
    catch (CosNaming::NamingContext::NotFound& ) 
    {
      // If the binding for that name does not
      // already exist on NameService, then create it...
      CosNaming::NamingContext_var dummy = 
                 root->bind_new_context(name);
    }

    // ... and to use the IDL-defined Interface
    // (interface or class) for the second entry.
    name.length(2);
    // (interface) class "IAdder"
    name[1].id = CORBA::string_dup( "IAdder" );
    
    // Create a Servant (of the implementation class),
    // and bind the servant object type to the name.
    ExampleInterfaces_IAdder_i adderServant;  
    PortableServer::ObjectId_var oid = 
            poa->activate_object(&adderServant);
    CORBA::Object_var adderServant_obj = 
            poa->id_to_reference(oid.in());
    root->rebind(name,adderServant_obj.in());
    
    cout << "IAdder interface has been" 
            " registered on the Naming Service" << endl;

    // Accept requests
    orb->run();
    orb->destroy();
  }
  catch(CORBA::Exception& ex) 
  {
    cerr << "Caught a CORBA exception: " << ex << endl;
    return 1;
  }
  return 0;
}

The C++ Linux TAO client that uses this server is the following (again, note the comments in the code):

// TAOAdderClient.cpp
//...

#include "ExampleInterfacesC.h"
#include <orbsvcs/CosNamingC.h>
#include <orbsvcs/Naming/Naming_Client.h>
#include <ace/streams.h>

int main( int argc, char *argv[] )
{
  try 
  {
    // Initialize the CORBA Object Request Broker
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv );

    // Find the CORBA Services Naming Service
    CORBA::Object_var naming_obj = 
       orb->resolve_initial_references("NameService");
    CosNaming::NamingContext_var root = 
             CosNaming::NamingContext::_narrow(naming_obj.in());
    if(CORBA::is_nil(root.in()))
    {
      cerr << "Could not narrow NameService to NamingContext!" << endl;
      throw 0;
    }

    // Resolve the desired object (ExampleInterfaces.IAdder).
    // The module and interface bindings
    // need to be the same here in the client as they
    // are in the server.
    CosNaming::Name name;
    name.length(2);
    // IDL-defined Module (namespace)
    name[0].id = CORBA::string_dup( "ExampleInterfaces" );
    
    // IDL-defined Interface (interface class)
    name[1].id = CORBA::string_dup( "IAdder" );
    
    CORBA::Object_var obj = root->resolve(name);

    // Narrow to confirm that we have the interface we want.
    ExampleInterfaces::IAdder_var iAdder = 
           ExampleInterfaces::IAdder::_narrow(obj.in());
    if (CORBA::is_nil(iAdder.in())) 
    {
      cerr << "Could not narrow to an iAdder reference" << endl;
      return 1;
    }

    // Now use the remote object...
    cout << "Using a remote object that implements" 
            " the IAdder interface..." << endl;
    cout << endl;
    double number1 = 0;
    double number2 = 0;
    double sum = 0;
    while (true)
    {
      cout << "Enter the first number: ";
      cin >> number1;
      cout << "Enter the second number: ";
      cin >> number2;
      sum = iAdder->add(number1, number2);
      cout << "The sum is: " << sum << endl;
      cout << "------------------" << endl;
    }
  }
  catch ( CORBA::Exception& ex ) 
  {
    cerr << "Caught a CORBA::Exception: " << ex << endl;
    return 1;
  } 
  return 0;
}

The Windows C# Client for the Linux ACE/TAO Server

And finally, here is the most important program of this example. This client, written in C# and running in Windows, will communicate through the TAO Naming Service with the ACE/TAO server running on Linux (or Windows). With a knowledge of the interface (obtained from the .idl) and the information about the name graph (obtained using the nslist.exe or nslister.exe utilities), a programmer should be able to modify this program to communicate with legacy C++ ACE/TAO servers (or IIOP.NET servers that use the Naming Service, as we will see in Example 3). Here is the important code:

C#
/// DotNetNSAdderClient.cs
/// ...

using System; 
using System.IO; 
using System.Runtime.Remoting; 
using System.Runtime.Remoting.Channels;

// from IIOPChannel.dll
using Ch.Elca.Iiop; 
using Ch.Elca.Iiop.Services; 
using omg.org.CosNaming; 

// from ExampleInterfaces.dll
using SLB.ExampleInterfaces;

namespace SLB.ExampleClients
{
  /// <summary>
  /// A simple NameService using client that connects
  /// to an ACE/TAO server through a TAO NameService.
  /// This client consumes a remote SLB.ExampleInterfaces.IAdder interface.
  /// </summary>
  class DotNetNSAdderClient
  {
//  private static string host = "localhost";
    // FAILS - must use a named or ip-address host.

    private static string host = "slb002";
    // named host
    private static int port = 2809;
    // OMG default port for iiop

    /// ...
    [STAThread]
    static void Main(string[] args)
    {
      try 
      {
        // Process the command line...
        ParseArgs(args);

        // Register the channel...
        IiopClientChannel channel = new IiopClientChannel();
        ChannelServices.RegisterChannel(channel);
 
        // Access the COS naming service (NameService)...
        CorbaInit init = CorbaInit.GetInit();
        NamingContext nameService = init.GetNameService(host, port);

        // Access the IDL-defined module
        // (which maps to a .Net namespace)...
        NameComponent[] moduleName = new NameComponent[] 
                        {new NameComponent("ExampleInterfaces", "")};
        NamingContext nameSpace = 
           (NamingContext)nameService.resolve(moduleName);

        // Access the IDL-defined interface
        // (which maps to a .NET interface class)
        NameComponent[] interfaceName = new NameComponent[] 
              {new NameComponent("IAdder", "")};
        SLB.ExampleInterfaces.IAdder servant = 
              (SLB.ExampleInterfaces.IAdder)nameSpace.resolve(interfaceName);

        // Use the servant...
        InteractWithUser(servant);
      } 
      catch (Exception e) 
      {
        /// ...
      }
    }
    /// ...
  }
  ///...
}

There are a few tricky bits:

  • A named host (or IP-address host) must be used to connect to the NameService. If "localhost" is used, an omg.org.CORBA.COMM_FAILURE is raised.
  • Module definitions in IDL, which map to namespaces in .NET, are treated as NamingContext objects on the TAO NameService. It is necessary to resolve all of the way through the nested set of NamingContexts (the omg.org.CosNaming.BindingType.ncontext types) to get to the desired interface (the omg.org.CosNaming.BindingType.nobject types).

This example can be run (all on Windows) using the Example2.bat file provided in the binary download. To run this example manually, do the following:

  1. Start the TAO Naming Service on the Linux machine: ./Naming_Service -m 0 -ORBEndpoint iiop://slb002:2809 -ORBDebugLevel 4.
  2. Start the C++ TAO-based Server on the Linux machine: ./TAOAdderServer -ORBInitRef NameService=iiop://slb002:2809/NameService.
  3. Start the C++ TAO-based Client on the Linux machine: ./TAOAdderClient -ORBInitRef NameService=iiop://slb002:2809/NameService.
  4. Start the IIOP.NET-based Client on the Windows machine: DotNetNSAdderClient.exe slb002 2809.

Example 3 - TAO Client and TAO Naming Service on Linux, IIOP.NET Server and IIOP.NET Client on Windows

This example will reuse the C++ TAO-based Client, the TAO Naming Service, and the IIOP.NET-based Client from Example 2, and will transparently substitute a new IIOP.NET-based Naming Service-using Server. The important code for this server is the following:

C#
/// DotNetNSAdderServer.cs
///...

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Threading;

// from ..\IIOPChannel.dll
using Ch.Elca.Iiop; 
using Ch.Elca.Iiop.Services; 
using omg.org.CosNaming; 

// from project ExampleInterfaces.dll
using SLB.ExampleInterfaces;

namespace SLB.ExampleServers
{
    /// <summary>
    /// The adder implementation class.
    /// </summary>
    public class Adder: MarshalByRefObject, IAdder 
    {
        ///... (See Implementation Above)
    }

    /// <summary>
    /// This .Net Server class object publishes
    /// an SLB.ExampleInterfaces.IAdder implementation
    /// and registers it on a TAO Naming Service
    /// using the iiop protocol.
    /// </summary>
    public class DotNetAdderServer 
    {

        //FAILS - seems to be associated with 
        //connecting to running Naming Service...
//      private static string host = "localhost";
        //use default named or IP host
        private static string host = "slb001";
        //default port
        private static int port = 12345;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        public static void Main(string[] args) 
        {
            try
            {
                ParseCommandLineArguments(args);

                // register the channel
                // assign port automatically
                IiopChannel chan = new IiopChannel(0);
                ChannelServices.RegisterChannel(chan);

                // Access the COS naming service (NameService)...
                CorbaInit init = CorbaInit.GetInit();
                NamingContext nameService = 
                      init.GetNameService(host, port);

                // Bind the IDL-defined module
                // (which maps to a .Net namespace)...
                NameComponent[] moduleName = 
                    new NameComponent[] 
                    {new NameComponent("ExampleInterfaces", "")};
                NamingContext nameSpace = 
                    nameService.bind_new_context(moduleName);

                // Bind the IDL-defined interface
                // (which maps to a .NET interface class)
                NameComponent[] interfaceName = 
                    new NameComponent[] 
                    {new NameComponent("IAdder", "")};
                Adder interfaceImplimentation = new Adder();
                nameSpace.bind(interfaceName,interfaceImplimentation);

                Console.WriteLine(".Net IAdder Server is Running...");
                Console.WriteLine("Clients can connect using" + 
                        " the TAO NamingService on Host:" + 
                        host + " Port:" + port.ToString());
                Thread.Sleep(Timeout.Infinite);
            }
            catch (Exception e)
            {
                ///...
            }
        }
        ///...
    }
}

Again, since we are connecting to a Naming Service, we need to use a named or IP host. Also, note how it is necessary to navigate the name graph using the bind_new_context() and bind() methods.

This example can be run (all on Windows) using the Example3.bat file provided in the binary download. To run this example manually, do the following:

  1. Start the TAO Naming Service on the Linux machine: ./Naming_Service -m 0 -ORBEndpoint iiop://slb002:2809 -ORBDebugLevel 4.
  2. Start the IIOP.NET-based Server on the Windows machine: DotNetNSAdderServer.exe slb002 2809.
  3. Start the C++ TAO-based Client on the Linux machine: ./TAOAdderClient -ORBInitRef NameService=iiop://slb002:2809/NameService.
  4. Start the IIOP.NET-based Client on the Windows machine: DotNetNSAdderClient.exe slb002 2809.

Conclusions

In this tutorial, you have seen how an ACE/TAO client written in C++, and running on Windows or Linux, can communicate directly with a .NET server, using the IIOP protocol. In addition, you have seen a technique that will let you write C# .NET clients, using IIOP.NET, that can communicate with legacy C++ ACE/TAO servers, running on Windows or Linux. Finally, you have also seen how you can write a C# IIOP.NET server that will use a Naming Service, and that can transparently replace an existing C++ ACE/TAO server.

The practical value of these techniques, using the excellent IIOP.NET package, should begin to be realized by .NET programmers who have until now been required to interoperate with legacy ACE/TAO code, and thought that the only way to do this effectively was to abandon the .NET framework and the C# language for the native C and C++ language and legacy frameworks of ACE/TAO. What I hope that these examples demonstrate is that continuing to use C++ - or deciding to use C# and .NET - to work with an ACE/TAO legacy is now an actual choice that a programmer can make.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer Defence R&D Canada
Canada Canada
Stephen Bogner is a Senior Research Engineer with Defence R&D Canada. As the Head Autonomous Applications Group, Autonomous Intelligent Systems Section, he only programs when it can't be avoided, and then only in C#.

Comments and Discussions

 
QuestionRegarding mixing of ACE+TAO and C# Pin
mkp2424-Aug-12 1:13
mkp2424-Aug-12 1:13 
QuestionCan you explain? Pin
Member 83193564-Nov-11 5:31
Member 83193564-Nov-11 5:31 
AnswerRe: Can you explain? Pin
Stephen Bogner7-Nov-11 4:37
Stephen Bogner7-Nov-11 4:37 
Generalcommunicate between two distinct OS environment Pin
sreejith ss nair10-Aug-07 3:41
sreejith ss nair10-Aug-07 3:41 
GeneralRe: communicate between two distinct OS environment Pin
Stephen Bogner20-Aug-07 9:36
Stephen Bogner20-Aug-07 9:36 
QuestionCORBA Error Pin
tdelaney31-Jul-07 10:39
tdelaney31-Jul-07 10:39 
AnswerRe: CORBA Error Pin
Stephen Bogner31-Jul-07 11:50
Stephen Bogner31-Jul-07 11:50 
GeneralNaming Service Pin
tdelaney27-Jul-07 7:07
tdelaney27-Jul-07 7:07 
GeneralRe: Naming Service Pin
Stephen Bogner30-Jul-07 3:53
Stephen Bogner30-Jul-07 3:53 
QuestionNeed help getting nslister to work Pin
Keith Mai12-Jan-06 13:54
Keith Mai12-Jan-06 13:54 
AnswerRe: Need help getting nslister to work Pin
Stephen Bogner13-Jan-06 13:44
Stephen Bogner13-Jan-06 13:44 
GeneralMany thanks Pin
Stefan Morrow3-Jan-06 20:50
Stefan Morrow3-Jan-06 20:50 
GeneralRe: Many thanks Pin
Stephen Bogner6-Jan-06 16:04
Stephen Bogner6-Jan-06 16:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.