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.
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.
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.
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]);
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!).
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?
To test this application we need a server working, a good excuse to go to the next lesson in the tutorial.