Introduction - A very simple client

We will start with a reasonably simple IDL interface: we want to build a stock quoter server, some interface to query the prices of stock. To make life interesting we will use a different CORBA object for each stock. This may sound like overkill, but it will motivate a number of elements that are interesting to learn at the start.

Defining the IDL interfaces

At the very least we want an operation to query the stock price, as in:

    interface Stock {
      double price ();
    };

but stocks usually have symbols and full names, so why not add a couple of attributes to query them:

    interface Stock {
      double price ();

      readonly attribute string symbol;
      readonly attribute string full_name;
    };

We also need some interface to gain access to the Stock object references from their symbols. Usually we call this kind of interface a Factory, and it looks like this:

  interface Stock_Factory {
    Stock get_stock (in string stock_symbol);
  };

Notice how arguments have a direction: they are qualified as input only (in), output only (out), or input-output (inout) arguments. There is a problem, though: what happens if the stock symbol is invalid? The CORBA way is to raise an exception:

  exception Invalid_Stock_Symbol {};

and then make the exception part of the Stock_Factory interface:

  interface Stock_Factory {
    Stock get_stock (in string stock_symbol)
      raises (Invalid_Stock_Symbol);
  };

Finally we put all these IDL constructs in a module to avoid namespace pollution to obtain the Quoter.idl file.

The Generated Files

Let's take a minute to look at the generated code. You don't need to do this often, in fact you rarely have to do it at all. But doing it once is educative and can demystify the role of the IDL compiler.

To generate the code you must invoke the IDL compiler, like this:

$ $TAO_ROOT/TAO_IDL/tao_idl Quoter.idl

The complete documentation of the IDL compiler and its options are included in compiler.html. Naturally this file is included in the distribution.

TAO generates 9 files for each IDL file. QuoterC.h, QuoterC.inl and QuoterC.cpp contain the client-side interfaces. Notice that the inline functions are in a separate file so you can optionally compile them out-of-line for smaller code. Pure clients only need to link the object file generated from QuoterC.cpp.

Similarly, QuoterS.h, and QuoterS.cpp contain the server-side skeletons. Servers must link the object files generated from QuoterS.cpp and QuoterC.cpp.

Finally, QuoterS_T.h, QuoterS_T.inl and QuoterS_T.cpp contain the TIE classes. These are the standard (after the CORBA 2.2 spec) skeletons based on composition instead of inheritance. They are in separate files only because some compilers cannot handle mixed template and non-template code in the same source file. You do not need to compile these files on any platform. However, the files are required to compile QuoterS.cpp. Also notice that if your platform does not support namespaces, then you may be unable to use the TIE approach for some IDL interfaces.

All the extensions and suffixes discussed above can be modified using options of the IDL compiler; check the documentation for more details. Notice, though, that you should use consistent extensions across your project, otherwise you may have problems with some #include directives in your IDL source.

Building a simple client

With our simple IDL interface ready, we want to start with a simple client. The first thing to do in any CORBA client or server is initialize the ORB:

int main (int argc, char* argv[])
{
  try {
    // First initialize the ORB, that will remove some arguments...
    CORBA::ORB_var orb =
      CORBA::ORB_init (argc, argv,
                       "" /* the ORB name, it can be anything! */);

IDL supports variable-length types whose sizes are not known at compile time, hence they must be dynamically allocated at run time. _var types relieve us of the explicit memory management of the variable-length types and thus hide the differences between fixed and variable-length structured types.

Since the ORB initialization can fail, and in fact, any CORBA operation can raise a CORBA::SystemException we use a try/catch block to check for any failures. Needless to say, this is very naive; some failures can be temporary, and we should have a better way to recover from errors, but this is enough for our example. In consequence, at the end of main() we catch all kinds of CORBA exceptions:

  }
  catch (const CORBA::Exception &ex) {
    std::cerr << "CORBA exception raised!" << std::endl;
  }
  return 0;
}

We must not forget that the ORB is a resource that must be released by the application. Until CORBA 2.3 there was no standard way to do this. TAO has adopted the new specification, so our client should really look like this:

int main (int argc, char* argv[])
{
  try {
    // First initialize the ORB, that will remove some arguments...
    CORBA::ORB_var orb =
      CORBA::ORB_init (argc, argv,
                       "" /* the ORB name, it can be anything! */);

    // the real code goes here!

    orb->destroy ();
  }
  catch (const CORBA::Exception &ex) {
    std::cerr << "CORBA exception raised!" << std::endl;
  }
  return 0;
}

Just a few words about the ORB name: The spec requires the ORB to return the same ORB pointer if the same ORB id is used in CORBA::init, and the implementation is free to return the same pointer even if different ids are used. Usually this is not a problem, as most applications instantiate a single ORB. TAO is one of the few CORBA implementations that actually supports multiple ORB pointers. This can be important for real-time applications where each ORB executes at a different priority.

Now that we have the ORB pointer, we can start bootstrapping the application. Normally we would use the Naming Service, Trading Service, or the Interoperable Naming Service to locate the stock factory, but for simplicity, let us use just an IOR string passed in the first argument.

The easiest way is to use the first argument to get the string, and then use string_to_object() to convert it into an object reference:

    CORBA::Object_var factory_object =
      orb->string_to_object (argv[1]);

    Quoter::Stock_Factory_var factory =
      Quoter::Stock_Factory::_narrow (factory_object.in ());

The _narrow() is used to test if a reference is of the specified type. If the reference is of the specified type, it returns a non-nil reference, else it returns a nil.

There are a few interesting things about TAO: First, it supports the file: scheme for object references, so the first argument could be file://a_file_name. In that case, the ORB will open the file named "a_file_name" and read the IOR from that file. TAO also supports the corbaloc: scheme, for example corbaloc:iiop:1.1@ace.cs.wustl.edu:12345/Stock_Factory. So using a string can be a very powerful bootstrapping protocol.

Before we go any further, at this point we are using interfaces generated by the IDL compiler, so we must include the correct header file!

#include "QuoterC.h"
Notice that this is all you need to include; the IDL compiler generates code that includes all the required internal header files. When you use TAO services, don't forget to include their corresponding header files too.

Another interesting TAO feature is the support for _unchecked_narrow(). This is part of the CORBA Messaging specification and essentially performs the same work as _narrow(), but it does not check the types remotely. If you have compile time knowledge that ensures the correctness of the narrow operation, it is more efficient to use the unchecked version.

Now we can use the rest of the arguments to obtain the stock objects:

    for (int i = 2; i != argc; ++i) {
      try {
        // Get the stock object
        Quoter::Stock_var stock =
          factory->get_stock (argv[i]);

Exercise 1

Complete the client implementation. It should be easy at this point, but it will give you a chance to set up your environment and become familiar with the basics of building a TAO application.

You don't need to do everything. The Quoter.idl file and a MPC file are provided. Just implement the client.cpp file. Cut & paste the ORB initialization code, and anything you find useful from above (you can even cheat and look at the solution, but it is going to be some really boring 30 minutes if you do!).

Solution

Look at the client.cpp file; it should not be much different from yours. Count the number of lines in your client, the idl file and the QuoterC.* files. Do you want to write all that code over again?

Testing

To test this application we need a server working, a good excuse to go to the next lesson in the tutorial.


Carlos O'Ryan
Last modified: Sun Apr 1 14:55:08 PDT 2001