Hello World C++ Server

The main thing we need our server application to do is incarnate our World: we need to create a servant that implements that interface. Before we get into any C++ code, recall that CORBA defines a number of standard IDL types and interfaces. To avoid polluting the global namespace, these definitions are provided within the IDL module called "CORBA" which corresponds to the C++ namespace called "CORBA." That's why you'll see a lot of types and functions prefixed by "CORBA::" in the code. The source code for our server is contained in the file, server.cpp. Let's analyze it line-by-line.


First, we include the header file that contains the definitions for the skeleton class from which we will derive our servant. The skeleton classes contain pure virtual functions that correspond to the interface methods in the IDL file. Note the 'S' suffix of the basename indicating that the file is required by server applications.
#include "HelloS.h"
	
By default, the TAO IDL compiler generates code that doesn't require C++ exceptions to be supported. The C++ mapping allows that, in lieu of exceptions, a CORBA::Environment structure may be passed as an additional parameter to each operation of a CORBA interface. The state of this structure may be queried after an invocation to determine whether an exception was raised. This can make for some pretty ugly code -- exceptions are much "cleaner." The -Ge option to tao_idl should prevent the inclusion of the unnecessary parameter, but this option is ignored at present. So, our implementations of the interface methods must contain this unused parameter. To make the code a little more bearable, I define this macro:
#define ENV CORBA::Environment& unnecessary_with_exceptions
	
Here is the definition for our World servant, World_impl. It is derived from our skeleton class, POA_World. We must override the pure virtual function, hello. Due to a bug in tao_idl, we must include a CORBA::Environment& parameter in our signature. Note that our return value is a char *.
class World_impl : public POA_World
{
    char * hello (ENV);
};
	
Here is our implementation of the hello method:
char *
World_impl::hello (ENV)
{
The C++ mapping makes the caller responsible for the deallocation of a variable-length result. In other words, whomever calls this function is going to try to delete the memory referenced by the return value, so we want to make sure we dynamically allocate it. Because the C++ operators, new and delete, are not portable across platforms, CORBA provides the functions, CORBA::string_alloc(), CORBA::string_free(), and CORBA::string_dup() which combines an allocation with a copy. Just so you're not too concerned about memory leaks in the server process, remember that the string allocated in this function is actually released by the skeleton code after it has sent a copy of it over the network to the client.
    return CORBA::string_dup("Greetings from C++ World server!!");
}
	
So much for our servant! Now we construct the obligatory main routine in which we'll instantiate it.
main (int argc, char** argv)
{
	
I'm assuming our C++ compiler supports exceptions, although neither CORBA nor TAO requires it. In fact, most of the TAO examples utilize somewhat clumsy macros (TAO_TRY, TAO_CATCH, etc.) to support either environment. Any invocation on a CORBA object reference may throw an exception, so they should always exist within a try-catch block.
    try {
	
You must always initialize the ORB by calling the function, CORBA::ORB_init(). For this tutorial, that's really all I should say, but I'm going to say more because there are some interesting things to say about the following line of code, and I'd rather not forget them.
  1. Initializing the ORB should be one of the first things you do in main(), definitely before you parse any command-line options, since ORB_init will remove from argv all of the ORB-specific options -- those beginning with "-ORB".
  2. ORB_init returns a reference to an ORB pseudo-object. A pseudo-object is something described in PIDL (Pseudo-IDL), which is almost identical to IDL. PIDL describes the rare interfaces that must be implemented by library code that ships with the ORB: they are not ordinary CORBA objects, they don't inherit from CORBA::Object and you can't pass them to operations on normal CORBA interfaces. Big deal. You'll never want to. Like I said, I'm telling you more than I should.
  3. Note the datatype used to store the return value: what the heck does that _var suffix mean? Essentially, it means that the memory allocated by ORB_init will be de-allocated automatically when the variable, orb, goes out of scope. It's like the C++ auto_ptr template. I'm going to talk more about _var types later when we build a client.
            // Initialize the ORB
        CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
	
Now we must obtain a POA (Portable Object Adapter). I'm going to talk more about POA's below, but right now let's concentrate on the ORB method, resolve_initial_references, which is a mechanism that allows you to portably bootstrap your CORBA application by obtaining necessary object references. The OMG standardizes the set of identifiers (strings) recognized by resolve_initial_references(). Currently, they are "RootPOA", "POACurrent", "InterfaceRepository", "NameService", "TradingService", "SecurityCurrent", and "TransactionCurrent". You may call list_initial_services to see which identifiers are supported by your ORB.
            // Get the Root POA
        CORBA::Object_var obj = orb->resolve_initial_references("RootPOA");
	
Because resolve_initial_references returns a CORBA::Object, we must down-cast the reference to the appropriate type, PortableServer::POA. For every IDL interface, the IDL compiler generates a static member function called _narrow() that does just this. It behaves very much like the C++ dynamic_cast template. If it fails, it returns a nil reference, similar to the way dynamic_cast would return 0. If it succeeds, however, it returns a copy of the reference converted to the new type. Therefore, it's important that both variables, obj and poa, are _var types that will free their memory when they go out of scope. Also, depending on the type we're narrowing, _narrow() may need to contact the server, so not only might it indicate failure by returning a nil reference, but it could possibly raise an exception.
        PortableServer::POA_var poa = PortableServer::POA::_narrow(obj);
	
Perhaps now is a good time to talk about object adapters. They are simply the means by which CORBA objects are linked (or adapted) to programming language servants. They are responsible for the creation of CORBA objects and their references and for dispatching requests to the proper servants. The standard CORBA object adapter is the Portable Object Adapter (POA) which provides features necessary for a programming language servant to be portable across different ORB implementations. All CORBA server applications contain at least one POA, the RootPOA, and possibly more, each with different characteristics. The discussion of those characteristics is beyond the scope of this tutorial. Each POA contains a reference to its POAManager, obtained by the operation, the_POAManager.
            // Activate POA manager
        PortableServer::POAManager_var mgr = poa->the_POAManager();
	
The POAManager controls the flow of requests into each POA, and that flow won't begin unless the POAManager is activated.
        mgr->activate();
	
So far, we've done the things required for every CORBA server application: initialize the ORB and the POA. Now we need to create our servant so that we may incarnate our CORBA object.
            // Create the servant(s)
        World_impl world_servant;
	
Ok, we created our servant, but we've not linked it to any CORBA object yet. That is accomplished by calling the deceptively simple _this method of our servant. That does four things:
  1. Creates a CORBA object under the RootPOA.
  2. Informs the RootPOA that world_servant is the implementation for that CORBA object.
  3. Creates an object reference for the new CORBA object.
  4. Returns the object reference.
            // Activate object implicitly by calling _this()
        World_var world = world_servant._this();
	
Ok, we're almost home. We still need to make ourself (our object reference) known to potential clients in the outside world. This is commonly done by associating our object reference with a name and registering that association with a Naming or Trader service. That's beyond the scope of this tutorial, though. Instead, we're going to stringify the object reference and echo it to standard output. The ORB interface provides two functions, object_to_string and string_to_object, that do exactly what their names imply: they convert a CORBA object reference to and from a human-readable string, respectively.
        
            // Write stringified reference to stdout
        CORBA::String_var str = orb->object_to_string(world);
	
Because the CORBA::String type defines a conversion operator that results in a const char *, we can do this:
        cout << str << endl;
	
Now the only thing left to do is run the orb. This starts an event loop that blocks awaiting client requests. The run method completes when the ORB's shutdown method is called. (This stupefyingly simple application has no facility for calling shutdown: we'll just kill the server when we're done with it and rely on the OS to clean up after us.)
            // Accept requests
        orb->run();
	
Our "try block" is complete, so now we catch any CORBA exception that may have been thrown from within it.
    }
    catch (const CORBA::Exception& e)
    {
	
The following line of code is the only non-portable code in this program. It is a convenient TAO extension that allows one to easily obtain the repository id of any exception. All CORBA vendors provide "extensions" in order to distinguish themselves. Can you blame them? Whether you consider it good or bad that they do, remember that because CORBA is an open standard, good ideas can be incorporated into the specification. The OMG is constantly "refactoring" CORBA.
        const char* err = e._id();
	
Give the user an idea of what happened...
        cerr << "Server caught CORBA exception: " << err << endl;
	
...and exit with a value indicating an error.
        return 100;
    }
	
Return success. We'll probably never reach this line of code because orb->run() will block until the process is terminated.
    return 0;
}
	

Whew! That's a lot to explain about such a small program. Let's see if writing a C++ client is any easier.

Back to... [ Corba Tutorials ]


Jim Crossley
Last modified: Tue Mar 23 17:39:54 EST 1999