next up previous
Next: Separating client and server Up: Sample Program Previous: Standalone program

MICO application

 

Now we want to turn the standalone implementation from the previous section into a MICO application. Because CORBA objects can be implemented in different programming languagesgif the specification of an object's interface and implementation have to be separated. The implementation is done using the selected programming language, the interface is specified using the so called Interface Definition Language (IDL). Basically the CORBA IDL looks like C++ reduced to class and type declarations (i.e., you cannot write down the implementation of a class method using IDL). Here is the interface declaration for our account object in CORBA IDL:

  interface Account {
      void deposit (in unsigned long amount);
      void withdraw (in unsigned long amount);
      long balance ();
  };

As you can see it looks quite similar to the class declaration in section 3.3.1. The in declarator declares amount as an input parameter to the deposit() and withdraw() methods. Usually one would save the above declaration to a file called account.idl.

The next step is to run this interface declaration through the IDL compiler that will generate code in the selected implementation programming language (C++ in our example). The MICO IDL compiler is called idl and is used like this:

  idl account.idl

The IDL compiler will generate two files: account.h and account.cc (see figure 3.2). The former contains class declarations for the base class of the account object implementation and the stub class a client will use to invoke methods on remote account objects. The latter contains implementations of those classes and some supporting code. For each interface declared in an IDL-file, the MICO IDL compiler will produce three C++ classesgif.

 figure170
Figure 3.2:   Creation process of a MICO application.

The three classes are depicted in figure 3.3 between the two dashed lines. The class Account serves as a base class. It contains all definitions which belong to the interface Account, like local declarations of user defined data structures. This class also defines a pure virtual function for each operation contained in the interface. The following shows a bit of the code contained in class Account:

  // Code excerpt from account.h
  class Account : virtual public CORBA::Object {
      ...
  public:
      ...
      virtual void deposit (CORBA::ULong amount) = 0;
      virtual void withdraw (CORBA::ULong amount) = 0;
      virtual CORBA::Long balance () = 0;
  }

The class Account_skel is derived from Account. It adds a dispatcher for the operations defined in class Account. But it does not define the pure virtual functions of class Account. The classes Account and Account_skel are therefore abstract base classes in C++ terminology. To implement the account object you have to subclass Account_skel providing implementations for the pure virtual methods deposit(), withdraw() and balance().

The class Account_stub is derived from class Account as well. In contrast to class Account_skel it defines the pure virtual functions. The implementation of these functions which is automatically generated by the IDL-compiler is responsible for the parameter marshalling. The code for Account_stub looks like this:

  // Code excerpt from account.h and account.cc
  class Account;
  typedef Account *Account_ptr;

  class Account_stub : virtual public Account {
      ...
  public:
      ...
      void deposit (CORBA::ULong amount)
      {
         // Marshalling code for deposit
      }
      void withdraw (CORBA::ULong amount)
      {
         // Marshalling code for withdraw
      }
      CORBA::Long balance ()
      {
         // Marshalling code for balance
      }
  }

This makes Account_stub a concrete C++ class which can be instantiated. The programmer never uses the class Account_stub directly. Access is only provided through class Account as will be explained later.

 figure197
Figure 3.3:   Inheritance relationship between stub- and skeleton classes.

It is worthwile to see where the classes Account and Account_skel are derived from. Account inherits from Object, the base class for all CORBA objects. This class is located in the MICO library. The more interesting inheritance path is for Account_skel. Account_skel inherits from MethodDispatcher, a class located again in the MICO library. This class is responsible for dispatching a method invocation. It maintains a list of method dispatchersgif. The class MethodDispatcher inherits from DynamicImplementation. This class represents the interface to the dynamic skeleton interface (DSI) which is described by the CORBA standard in chapter [18.4.5]. This is a notably feature of MICO: the code generated by the MICO IDL compiler makes use of CORBAs DII and DSI.

Up until now we have written the interface of an account object using CORBA IDL, saved it as account.idl, ran it through the IDL compiler which left us with two files called account.cc and account.h that contain the class declarations for the account implementation base class (Account_skel) and the client stub (Account_stub). Figure 3.2 illustrates this. What is left to do is to subclass Account_skel (implementing the pure virtual methods) and write a program that uses the bank account. Here we go:

 1: #include "account.h"
 2: 
 3: class Account_impl : virtual public Account_skel
 4: {
 5: private:
 6:   CORBA::Long _current_balance;
 7: 
 8: public:
 9:   Account_impl()
10:   {
11:     _current_balance = 0;
12:   };
13:   void deposit( CORBA::ULong amount )
14:   {
15:     _current_balance += amount;
16:   };
17:   void withdraw( CORBA::ULong amount )
18:   {
19:     _current_balance -= amount;
20:   };
21:   CORBA::Long balance()
22:   {
23:     return _current_balance;
24:   };
25: };
26: 
27: 
28: int main( int argc, char *argv[] )
29: {
30:   // ORB initialization
31:   CORBA::ORB_var orb = CORBA::ORB_init( argc, argv, "mico-local-orb" );
32:   CORBA::BOA_var boa = orb->BOA_init( argc, argv, "mico-local-boa" );
33: 
34:   // server side
35:   Account_impl* server = new Account_impl;
36:   CORBA::String_var ref = orb->object_to_string( server );
37:   cout << "Server reference: " << ref << endl;
38:   
39:   //----------------------------------------------------------------------
40:   
41:   // client side
42:   CORBA::Object_var obj = orb->string_to_object( ref );
43:   Account_var client = Account::_narrow( obj );
44: 
45:   client->deposit( 700 );
46:   client->withdraw( 250 );
47:   cout << "Balance is " << client->balance() << endl;
48: 
49:   // We don't need the server object any more. This code belongs
50:   // to the server implementation
51:   CORBA::release( server );
52:   return 0;
53: }

Lines 3-25 contain the implementation of the account object, which is quite similar to the implementation in section 3.3.1. Note that the class Account_impl inherits the from class Account_skel, which contains the dispatcher for this interface, via a virtual public derivation. Although the keyword virtual is not required in this case, it is a good practise to write it anyway. This will become important when interface inheritance is discussed in section 5.5.

The main() function falls into two parts which are seperated by the horizontal line (line 39): Above the separator is the server part that provides an account object, below the line is the client code which invokes methods on the account object provided by the server part. Theoretically the two parts could be moved to two seperate programs and run on two distinct machines and almost nothing had to be changed in the code. This will be shown in the next section.

In line 32 the MICO initialization function is used to obtain a pointer to the Object Request Broker (ORB) object--a central part of each CORBA implementation. Among others the ORB provides methods to convert object references into a string representation and vice versa. In line 35 an account object called server is instantiated. Note that it is not permitted to allocate CORBA objects on the run-time stack. This is because the CORBA standard prescribes that every object has to be deleted with a special function called CORBA::release(). Automatic allocation of an object would invoke its destructor when the program moves out of scope which is not permissible. In our little sample program the server object is deleted explicitly in line 51.

In line 36 the ORB is used to convert the object reference into a string that somehow has to be transmitted to the client (e.g., using Email, a name service or a trader). In our example client and server run in the same address space (i.e. the same process) so we can turn the string into an object reference back again in line 42. Line 43 uses the Account::_narrow() method to downcast the object reference to an Account_var. The rest of main() just uses the account object which was instantiated in line 35.

Account_var is a smart pointer to Account instances. That is an Account_var behaves like an Account_ptr except that the storage of the referenced object is automatically freed via the aforementioned release() function when the Account_var is destroyed. If you use Account_ptr instead you would have to use CORBA::release() explicitly to free the object when you are done with it (never use delete instead of CORBA::release()).

Assuming the above code is saved to a file called account_impl.cc you can compile the code like thisgif:

  mico-c++ -I. -c account_impl.cc -o account_impl.o
  mico-c++ -I. -c account.cc -o account.o
  mico-ld -I. -o account account_impl.o account.o -lmico2.2.3

This will generate an executable called account. Running it produces the following output:

  Server reference: IOR:1200000069643a6d69636f2d6c6f63616c2d626f61310\
  1000000b77a0000160000001200000069643a6d69636f2d6c6f63616c2d626f6131
  Balance is 450

You can find the source code for this example in the demo/account directory within the MICO source tree.


next up previous
Next: Separating client and server Up: Sample Program Previous: Standalone program

MICO
Tue Nov 10 11:04:45 CET 1998