by Bryan Morgan

Applied CORBA: Integrating legacy code with the Web

news
Jan 1, 199818 mins

Find out how to Webify existing object-oriented code

In my previous article “CORBA meets Java” (JavaWorld, October 1997), I provided a general introduction to the CORBA. In particular, I showed you how to develop 100% Pure Java applications using one popular ORB product. As you may recall, we discovered that application development using CORBA holds a number of advantages over RMI and DCOM, including support for legacy code, scalability, wide platform support, end-to-end security, and more. (If all this sounds a little hazy to you, you might want to take a moment to read my first article before continuing on.)

This month, we’ll focus on CORBA’s support for legacy code. Because CORBA is a language-independent technology, any client written in a language with CORBA support (Java, C++, Ada, SmallTalk, COBOL, etc.) can access server objects in a platform- and language-independent manner.

To demonstrate this feature, we’re going to extend the basic Java applet and Java server application that we created in the previous article by trading in the Java server for a new server application written in C++. Our Java client applet will remain completely unchanged. Then, we’ll extend both the client and the server to explore additional features of the CORBA IDL, which we briefly touched on back in October. For those readers familiar with JavaWorld‘s extensive coverage of Java programming issues, you may be taken aback to find a preponderance of C++ code in this article. This code is necessary to show that CORBA servers and clients can communicate and share data regardless of which language they are written in. I chose to use C++ because it is the most popular (in terms of usage) object-oriented programming language, and also because the majority of existing CORBA applications have been written in C++. My future articles on CORBA and distributed object computing (yes, that’s right; JavaWorld is expanding its coverage ot this topic) will focus almost exclusively on Java.

Because of the different ways in which C++ and Java handle memory usage, stub and skeleton code generated by the C++ and Java IDL compilers can vary significantly. The beauty of CORBA lies in the fact that Java developers can simply ask for and receive remote objects without worrying about the implementation details contained within those objects. Likewise, the server object developer (writing in C++ or some other language) need not be concerned with which language will be accessing the object being created.

ORBInfo in C++

In the October article, I used the Visigenic VisiBroker for Java 3.0 ORB to create a server object named

ORBInfo

. The interface to this object was described using the following IDL interface:

module ORBQuery
{
     interface ORBInfo
     {
          string GetName(in long index);
          string GetVendor(in long  index);
          string GetOS(in long index);
          string GetLanguages(in long  index);
          string GetURL(in long  index);
     };
};

This month, we’ll create our first example using this same interface. This time, however, we’ll create a server application using the Visigenic VisiBroker for C++ 3.0 ORB, which you can download on a free trial basis from Visigenic Software (see Resources). The VisiBroker for C++ ORB offers many of the features included with VisiBroker for Java, including the Smart Agent Interface Repository, and support for Dynamic Invocation and Server Interfaces. As a Java developer, you’ll be interested to know that with the new 3.0 release of VisiBroker for C++, several of the main components of the ORB are written in Java, showing that yet another major software vendor is practicing what they preach when it comes to Java development. Components written in Java include the idl2cpp (IDL-to-C++) compiler, the Interface Repository, and the Object Activation Daemon registration utilities.

Before using the idl2cpp compiler included with VisiBroker, let’s take a moment to examine the CORBA C++-language bindings. When developing CORBA applications in multiple languages, you must have a complete understanding of each languages’ bindings to help you better understand how data types are marshalled (“packaged” between one system and another) and what code is going to be generated by your ORB’s IDL compiler. Bindings are the rules by which IDL definitions are converted to the language of your choice and also how the resulting code is used. Because of the popularity of C++, virtually every ORB vendor offers a CORBA-compliant C++ ORB. Like all other standards-compliant software, it is important to analyze your tool of choice and identify which features are fully compliant and which ones are vendor enhancements.

Mapping for the basic data types

Table 1 lists the mappings between the primitive IDL data types and the resulting VisiBroker and C++ types.

IDL TypeVisiBroker TypeC++ Definition
shortCORBA::Shortshort
longCORBA::LongPlatform dependent
unsigned shortCORBA::UShortunsigned short
unsigned longCORBA::ULongunsigned long
floatCORBA::Floatfloat
doubleCORBA::Doubledouble
charCORBA::Charchar
booleanCORBA::Booleanunsigned char
octetCORBA::Octetunsigned char
longdoubleCORBA::LongDoublePlatform dependent
longlongCORBA::LongLongPlatform dependent
ulonglongCORBA::ULongLongPlatform dependent
wcharCORBA::WCharPlatform dependent
wstringCORBA::WStringPlatform dependent

As with Java, the mapping for each IDL attribute produces two member functions. Both of these functions have the same name as the attribute. One function is required to retrieve the attribute’s value and the other function is required to set the attribute’s value. The biggest difference between C++ code and Java code generated from the same IDL interface is the way the out or inout parameters are handled. In C++ it is standard practice to pass references and/or pointers to objects as parameters to C++ methods and have the value of these objects modified and returned. (C++ allows you to directly access an object’s memory location.) As an example, examine the following IDL interface:

interface A
{
    void SomeMethod(in long  x, inout long y, out long z);
};

When you compile this code with the idl2cpp compiler included with VisiBroker, the following C++ method for SomeMethod() is produced:

virtual void SomeMethod(CORBA::Long _x, CORBA::Long& _y, CORBA::Long& _z);

Compiling this same interface with the VisiBroker idl2java compiler produces the following method definition:

public void SomeMethod(int x, org.omg.CORBA.IntHolder y, org.omg.CORBA.IntHolder z);

Notice that in the C++ output the out variables (y and z) are defined as being passed by reference, but in the Java output, two org.omg.CORBA.IntHolder variables are passed instead. Because the client and server exist in separate memory spaces, the actual memory addresses of the objects are not passed. Instead, the ORB handles the marshalling of data across process and machine boundaries, leaving the developer free to use the results of the call to SomeMethod().

Building the ORBInfo server in C++

The steps required to build a CORBA server in C++ are identical those required to build a server in Java. In short, you must complete the following steps before attempting to connect to our server from an existing client:

  1. Set up the development/runtime environment by modifying the PATH and by setting the appropriate environment variables

  2. Write a definition for each object using IDL

  3. Compile the IDL code to produce the server skeleton code

  4. Implement the server object in code

  5. Compile the server

  6. Start the server

To simplify this example, I will assume that you have installed the VisiBroker for C++ ORB on your machine and already completed Step 1. We created the ORBInfo interface for our original all-Java CORBA example , so Step 2 is also taken care of. To produce the server’s skeleton code, simply compile the ORBInfo.idl interface using the idl2cpp IDL compiler like this:

% idl2cpp ORBInfo.idl

This operation will produce four source files:

  • ORBInfo_c.hh
  • ORBInfo_c.cc
  • ORBInfo_s.hh
  • ORBInfo_s.cc

These files represent the generated classes for the client (ending with the _c) and the server (ending with the _s). We’ll ignore the client code because we already have a working Java client that uses this interface. Within the server code is class _sk_ORBInfo. This is the base class from which we’ll derive our implementation class. Within _sk_ORBInfo are five virtual functions that correspond to the five functions defined in our ORBInfo interface. The signatures for these functions are:

virtual char* GetName(CORBA::Long index) = 0;
virtual char* GetOS(CORBA::Long index) = 0;
virtual char* GetLanguages(CORBA::Long index) = 0;
virtual char* GetVendor(CORBA::Long index) = 0;
virtual char* GetURL(CORBA::Long index) = 0;

Using the capabilities of C++, we can build our implementation object by deriving from the _sk_ORBInfo base class and implementing each of these functions. A standard CORBA naming convention is to append Impl to the end of your object implementation, so we will name our C++ object implementation ORBInfoImpl. The following code segment shows the contents of the ORBInfoImpl class. (Note that the functionality here corresponds to that of the Java ORBInfo server we developed in the October article.)

class ORBInfoImpl: public _sk_ORBQuery::_sk_ORBInfo
{
    public:
    ORBInfoImpl(const char *object_name) : _sk_ORBQuery::_sk_ORBInfo(object_name) 
    {
        ORBVendors[0][5] = ("PowerBroker","Orbix","VisiBroker","ComponentBroker","Solaris NEO");
        ORBVendors[1][5] = ("Expersoft Corp.","Iona Technologies","Visigenic Software","IBM","Sun");
        ORBVendors[2][5] = ("OLE and ActiveX Bridges; Windows95/NT; Solaris; HP-UX; AIX; JDK 1.0.2",
          "Windows95/NT, MVS, OS/2, QNX, VxWorks, Solaris, HP-UX, Irix, AIX, Digital UNIX, OLE Bridge",
                  "Windows95/NT, Sun OS, Solaris, HP-UX, AIX, Irix",
          "Windows95/NT, Solaris, HP-UX, AIX, OS/390, OS/2, AS/400",
          "Solaris (Client & Server), Windows95/NT (client), JDK 1.0.2");
        ORBVendors[3][5] = ("C++, Java", "Java, Smalltalk, Ada95, C++", "Java, C++", "Java, C++", "Java, C++");
        ORBVendors[4][5] = ("http://www.expersoft.com", "http://www.iona.com", "http://www.visigenic.com",
          "http://www.software.ibm.com/ad/cb", "http://www.sun.com/solaris/neo/solaris_neo/index.html");
    }
    char* GetName(CORBA::Long index)
    {
        char* Name;
        Name = ORBVendors[0][index];
        return Name;
 
    char* GetVendor(CORBA::Long index)
    {
        char* Vendor;
        Vendor = ORBVendors[1][index];
        return Vendor;
    }
    char* GetOS(CORBA::Long index)
    {
        char* OS;
        OS = ORBVendors[2][index];
        return OS;
    }
    char* GetLanguages(CORBA::Long index)
    {
        char* Languages;
        Languages = ORBVendors[3][index];
        return Languages;
    }
    char* GetURL(CORBA::Long index)
    {
        char* URL;
        URL = ORBVendors[4][index];
        return URL;
    }
    private:
        char* ORBVendors[5][5];
};

As you can see, developing apps in C++ or Java using the VisiBroker ORB is essentially the same. For instance, both the Java and C++ IDL compilers produce base skeleton classes containing stub functions for your implementation class to override. And the similarities don’t end there! The following code listing shows the main() function required to initialize the ORB and BOA and then register the ORBInfo object with the ORB. The notation “BOA” refers to the ORB’s Basic Object Adapter, which handles the process of registering object implementations and managing their activation policies. Note that this code is virtually identical to the Java server startup code we created in the previous article.

int main(int argc, char* const* argv) { try { // Initialize the ORB and BOA. CORBA::ORB_ptr orb = CORBA::ORB_init(argc, argv); CORBA::BOA_ptr boa = orb->BOA_init(argc, argv);

// Create a new ORBInfo object. ORBInfoImpl ORBInfo("JavaWorldORBInfo");

// Export the newly created object. boa->obj_is_ready(&ORBInfo); cout << "ORBInfo is ready." << endl;

// Wait for incoming requests boa->impl_is_ready(); } catch(const CORBA::Exception& e) { cerr << e << endl; } }

Extending the ORBInfo server

Our ORBInfo application serves only as a simplistic demonstration of the basic fundamentals of CORBA development using both Java and C++ with the Visigenic VisiBroker ORB. Now that you’ve got the fundamentals securely under your belt, we’re going to shake everything up again!

Our next step is to modify the Java client and C++ server applications so the client can retrieve an entire ORBInfo structure containing an ORB’s name, vendor, OS, languages, and URL from the server rather than calling separate methods to retrieve these items individually. To do so, we will make use of a data structure within IDL known as a struct. A struct can contain multiple variables of varying data types.

IDL also provides a way for us to pass an entire list of structs from server to client: a sequence. Because we need to retrieve data structures and also iterate through a list of these data structures, we will modify our interface definition, shown next, to define both a struct and a sequence of these structs.

module ORBQuery { struct ORBInfoStruct { string Name; string Vendor; string OS; string Languages; string URL; };

typedef sequence<ORBInfoStruct, 5> ORBInfoList;

interface ORBInfo { ORBInfoStruct GetORBInfo(in long index); ORBInfoList GetORBInfoList(); }; };

When you compile this code with the idl2cpp compiler, the same set of filenames that were constructed earlier are reconstructed for client and server objects. However, this time the internal stubs and skeletons have changed to match up with our modified IDL. Most notably, the five member methods used to retrieve individual data elements about each ORB have been replaced by the following two methods:

virtual ORBQuery::ORBInfoList* GetORBInfoList() = 0;
virtual ORBQuery::ORBInfoStruct* GetORBInfo(CORBA::Long index) = 0;

These two functions represent the mapping between what we described using IDL and the corresponding C++ code. An ORBQuery::ORBInfoStruct is defined in C++ as the following:

struct ORBInfoStruct {
    CORBA::String_var Name;
    CORBA::String_var Vendor;
    CORBA::String_var OS;
    CORBA::String_var Languages;
    CORBA::String_var URL;
  };

This segment is virtually identical to what we described in the IDL listing; whether a struct is in C++, Java, or IDL, it represent the same data structure.

The only item left for us to examine is the ORBInfoList sequence. The C++ mapping defines that an IDL sequence be converted to a C++ class containing data that can be accessed via array notation. This class also contains member functions for setting the size of the internal data to be stored and for retrieving the internal data. Below is a partial listing of the ORBInfoList class created by the IDL compiler.

class ORBInfoList { public: static ORBInfoStruct *allocbuf(CORBA::ULong _nelems); static void freebuf(ORBInfoStruct *_data); ORBInfoList(); ORBInfoList(CORBA::ULong _len, ORBInfoStruct *_data, CORBA::Boolean _release=0); ORBInfoList(const ORBInfoList&);

static CORBA::ULong maximum() { return 5; } void length(CORBA::ULong _len); CORBA::ULong length() const { return _count;} static ORBInfoList *_duplicate(ORBInfoList* _ptr) { if (_ptr) _ptr->_ref_count++; return _ptr; } };

For example, to use the ORBInfoList class, we first set its size (in this case 5) using the following statements:

ORBInfoList tempList;
tempList.length(5);

To modify the data members of an ORBInfoStruct within the list, issue the following statements in your implementation code:

tempList[0].Name = "PowerBroker"; tempList[0].Vendor = "ExperSoft"; tempList[0].OS = "OLE and ActiveX Bridges; Windows95/NT; Solaris; HP-UX; AIX; JDK 1.0.2";

tempList[0].Languages = "C++, Java";

tempList[0].URL =

<a href="https://www.expersoft.com/">http://www.expersoft.com</a>

;

Modifying our server implementation class to take advantage of these changes is simply a matter of implementing the GetORBInfoList() and GetORBInfo() functions. The original main() method stays the same because nothing has changed in the way we initialize the environment and register our object. The two modified functions are shown next and make use of the private variable ORBVendors within our implementation class:

ORBQuery::ORBInfoList* ORBInfoImpl::GetORBInfoList()

{

return &ORBVendors; }

ORBQuery::ORBInfoStruct* ORBInfoImpl::GetORBInfo(CORBA::Long index) { return &(ORBVendors[index]); }

You can now compile and run our extended server application.

Our next task is to modify our Java client to take advantage of the sleek new interface.

Extending the ORBInfo Java client

Although it may seem like incorporating the server changes into the client will be a lot of work, it’s really quite simple — remember, all implementation details are hidden from the client. We simply need to take out the repetitive calls to the server to retrieve each element of information on an ORB vendor. Instead, we can make one call to retrieve a single vendor or, better yet, we can make one call to retrieve an ORBInfoList sequence. Once we make this call, we will no longer need to call back to the server each time the user presses a button. Before diving in and modifying the applet’s code, let’s take a moment to examine the Java code that was generated from the IDL. The following two methods are now in the ORBInfo interface representing the two new methods in our IDL definition:

public ORBQuery.ORBInfoStruct GetORBInfo(int index);

public ORBQuery.ORBInfoStruct[] GetORBInfoList();

Note that instead of defining a new sequence class and complicating matters, the IDL compiler simply returns an array of ORBInfoStruct‘s from the GetORBInfoList() method. To access the server, we follow the same steps as before in order to initialize the ORB and retrieve the remote object. Once we have retrieved an ORBInfo object, we can call its GetORBInfoList() method to access the ORBInfoList sequence. This can all be done within the applet’s init() method, which means that only one call will be made to the server per client connection. Below is the new applet code. All modifications are highlighted in red.

import java.awt.*; import java.applet.*; import java.net.*; import ORBQuery.*;

public class ClientApplet extends Applet { void LoadPageButton_Clicked(Event event) {

//{{CONNECTION // Invalidate the TextField

try

{ URL siteURL = new URL(URLTextField.getText()); getAppletContext().showDocument(siteURL); } catch (java.net.MalformedURLException error) { System.out.println("Vendor home page retrieval failed!"); } //}} }

void NextButton_Clicked(Event event) {

//{{CONNECTION // Set the text for TextField... if (index + 1 <= 4) { index = index + 1; VendorTextField.setText(orbInfoList[index].Vendor); ProductTextField.setText(orbInfoList[index].Name); OSTextField.setText(orbInfoList[index].OS); LanguageTextField.setText(orbInfoList[index].Languages); URLTextField.setText(orbInfoList[index].URL); } //}} }

void PrevButton_Clicked(Event event) {

//{{CONNECTION // Set the text for TextField... if (index - 1 >= 0) { index = index - 1; VendorTextField.setText(orbInfoList[index].Vendor); ProductTextField.setText(orbInfoList[index].Name); OSTextField.setText(orbInfoList[index].OS); LanguageTextField.setText(orbInfoList[index].Languages); URLTextField.setText(orbInfoList[index].URL); } //}} }

ORBQuery.ORBInfo orbInfo; ORBQuery.ORBInfoStruct[] orbInfoList; int index = 0;

public void init() { super.init();

//{{INIT_CONTROLS setLayout(null); addNotify(); resize(426,306); label1 = new java.awt.Label("CORBA ORB's Vendor Comparison"); label1.reshape(60,12,300,39); label1.setFont(new Font("Helvetica", Font.BOLD, 16)); label1.setForeground(new Color(255)); add(label1); NextButton = new java.awt.Button("Next"); NextButton.reshape(180,252,87,30); add(NextButton); PrevButton = new java.awt.Button("Previous"); PrevButton.reshape(72,252,87,30); add(PrevButton); LoadPageButton = new java.awt.Button("Load Page"); LoadPageButton.reshape(288,252,87,30); add(LoadPageButton); label2 = new java.awt.Label("Vendor:"); label2.reshape(60,60,74,16); add(label2); label3 = new java.awt.Label("Product:"); label3.reshape(60,96,77,17); add(label3); label4 = new java.awt.Label("OS Support:"); label4.reshape(60,132,86,20); add(label4); label5 = new java.awt.Label("Languages:"); label5.reshape(60,168,72,24); add(label5); label6 = new java.awt.Label("Home Page:"); label6.reshape(60,204,84,19); add(label6); VendorTextField = new java.awt.TextField(); VendorTextField.reshape(156,60,254,22); VendorTextField.setFont(new Font("Dialog", Font.PLAIN, 8)); add(VendorTextField); ProductTextField = new java.awt.TextField(); ProductTextField.reshape(156,96,254,22); ProductTextField.setFont(new Font("Dialog", Font.PLAIN, 8)); add(ProductTextField); OSTextField = new java.awt.TextField(); OSTextField.reshape(156,132,254,22); OSTextField.setFont(new Font("Dialog", Font.PLAIN, 8)); add(OSTextField); LanguageTextField = new java.awt.TextField(); LanguageTextField.reshape(156,168,254,22); LanguageTextField.setFont(new Font("Dialog", Font.PLAIN, 8)); add(LanguageTextField); URLTextField = new java.awt.TextField(); URLTextField.reshape(156,204,254,22); URLTextField.setFont(new Font("Dialog", Font.PLAIN, 8)); add(URLTextField); //}}

try { // Initialize the ORB (using the Applet). org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(this);

// Create the ORBInfo interface object. orbInfo= ORBInfoHelper.bind(orb, "JavaWorldORBInfo");

orbInfoList = orbInfo.GetORBInfoList(); VendorTextField.setText(orbInfoList[index].Vendor); ProductTextField.setText(orbInfoList[index].Name); OSTextField.setText(orbInfoList[index].OS); LanguageTextField.setText(orbInfoList[index].Languages); URLTextField.setText(orbInfoList[index].URL);

} catch(org.omg.CORBA.SystemException e) {

System.out.println(e); }

}

public boolean handleEvent(Event event) { if (event.target == PrevButton && event.id == Event.ACTION_EVENT) { PrevButton_Clicked(event); return true; } if (event.target == NextButton && event.id == Event.ACTION_EVENT) { NextButton_Clicked(event); return true;

} if (event.target == LoadPageButton && event.id == Event.ACTION_EVENT) { LoadPageButton_Clicked(event); return true; } return super.handleEvent(event); }

//{{DECLARE_CONTROLS java.awt.Label label1; java.awt.Button NextButton; java.awt.Button PrevButton; java.awt.Button LoadPageButton; java.awt.Label label2; java.awt.Label label3; java.awt.Label label4; java.awt.Label label5; java.awt.Label label6; java.awt.TextField VendorTextField; java.awt.TextField ProductTextField; java.awt.TextField OSTextField; java.awt.TextField LanguageTextField; java.awt.TextField URLTextField; //}} }

Conclusion

As the examples in this article demonstrate, CORBA supports full interoperability between clients and servers regardless of what operating system they are running under and what programming languages they are written in.

If you are interested in converting existing C++ (or some other language code) over for use in a future distributed application using CORBA, your chances of success greatly increase if the legacy project was developed using a proper object design. If objects were created properly, building the interface definitions and then generating the associated skeleton/stub code will be very straightforward. From there, simply plug your existing objects into the implementation class that you’ve derived from the base skeleton class. Once this process is complete, you will have a functioning object or set of objects that can be accessed via CORBA from either Java applets or Java applications running in remote locations. A key factor leading to the success or failure of a project of this type is the original object design that was used to build your legacy project. If objects within the original application cannot be easily modeled using IDL, a redesign will be required so that Java objects can talk to legacy objects effectively.

During the coming months, I will be expanding my coverage of distributed objects through a bi-monthly JavaWorldcolumn on the subject. Together, we will examine the world of distributed objects as it applies to the Java developer including new technologies, products, and industry movements. If you have specific subjects you would like to see addressed, be sure to contact me via e-mail.

Bryan Morgan is a senior member of the technical staff with TASC Inc.. He currently is using CORBA and Java to build a distributed spatial query application as part of an internal R&D project. Bryan has co-authored several books for Sams Publishing, including Visual J++ Unleashed and Java Developer’s Reference. He holds a B.S. in Electrical Engineering from Clemson University.