Example Client Application

To understand how to use the C++ client API, we will examine a simple application. Similar to the Java/C++ application we reviewed in Chapter 6, Putting It All Together, this application makes use of an IDL-specified interface, and a C++ server. The key differences, then, are that there is no Java code in this case, and we will not need to use XML.

All the code shown here, including a GNU makefile, can be found in the directory $TWEEK_BASE_DIR/share/tweek/test/CxxClient. In the following sections, we will refer to the specific files and highlight key sections within them. We assume that readers have already read and understood Chapter 6, Putting It All Together. This is important because we will refer back to concepts illustrated in that chapter. Further, we will not present the server code in this chapter because it is nearly identical to the code for the server discussed previously. Interested readers can review the file server.cpp.

StringSubject Interface

As usual, we begin by defining an interface. For this example, we will use an interface that provides access to a simple string object. The server (Subject) will maintain the value of the string, and the clients (Observers) will be able to query and manipulate the string. The interface will be called CxxClientTest::StringSubject. The code for the interface is shown in Example 7.1, “StringSubject.idl”.

Example 7.1. StringSubject.idl

#ifndef _CXX_CLIENT_TEST_STRING_SUBJECT_IDL_
#define _CXX_CLIENT_TEST_STRING_SUBJECT_IDL_

#include <tweek/idl/Subject.idl>

module CxxClientTest
{
   interface StringSubject : tweek::Subject   1
   {
      void setValue(in string val);           2
      string getValue();                      3
   };
};

#endif /* _CXX_CLIENT_TEST_STRING_SUBJECT_IDL_ */
1

As with all user-defined Subjects, we derive our interface from tweek::Subject.

2 3

These methods define the accessors for the encapsulated string value. Clients will invoke these to query and to manipulate the state of the remote Subject.

StringSubject Interface Implementation

Now that we have our interface defined abstractly, we must provide a C++ implementation of the Subject. This will be done in the files StringSubjectImpl.cpp and StringSubjectImpl.h. We begin with the header file, shown in Example 7.2, “StringSubjectImpl.h”. While the example code may appear long, there is only slight variation from previous Subject implementation header files. We highlight the important bits, of course.

Example 7.2. StringSubjectImpl.h

  1 #ifndef _STRING_SUBJECT_IMPL_H_
    #define _STRING_SUBJECT_IMPL_H_
    
    #include <tweek/tweekConfig.h>
  5 
    #include <string>
    
    #include <vpr/vpr.h>
    #include <vpr/Sync/Mutex.h>
 10 #include <tweek/CORBA/SubjectImpl.h>
    #include <StringSubject.h>                                               1
    
    namespace CxxClientTest
    {
 15 
    /**
     * This class is an extension to the base Tweek SubjectImpl class.  It uses
     * multiple inheritance with that class and with the generated CORBA class
     * corresponding to the IDL for StringSubject.
 20  */
    class StringSubjectImpl : public POA_CxxClientTest::StringSubject,       2
                              public tweek::SubjectImpl
    {
    public:
 25    StringSubjectImpl() : tweek::SubjectImpl(), mValue("")
       {
          /* Do nothing. */ ;
       }
    
 30    virtual ~StringSubjectImpl()
       {
          /* Do nothing. */ ;
       }
    
 35    /**
        * Sets this subject's internal value.
        */
       virtual void setValue(const char* value);                             3
    
 40    /**
        * Returns this subject's internal value.
        */
       virtual char* getValue();                                             4
    
 45    /**
        * This overriding method is needed so that the correct type is returned
        * when the _this() method is invoked.  Without this method, an object of
        * type tweek::Subject_ptr would be returned.
        */
 50    CxxClientTest::StringSubject_ptr _this()                              5
       {
          return POA_CxxClientTest::StringSubject::_this();
       }
    
 55 private:
       std::string mValue;      /**< Our value */                            6
       vpr::Mutex  mValueLock;  /**< A mutex to protect mValue accesses */   7
    };
    
 60 } // End of CxxClientTest namespace
    
    #endif /* _STRING_SUBJECT_IMPL_H_ */
1

We must be sure to include the header that defines the base class from which we will derive our implementation. Remember that this code will be generated by the IDL compiler.

2

As usual, we declare our implementation so that it derives from the class generated by the IDL compiler (POA_CxxClientTest::StringSubject) and from the basic Tweek Subject implementation class (tweek::SubjectImpl).

3 4

These are the pure virtual methods defined by POA_CxxClientTest::StringSubject that we must implement. As in previous examples, this is where we will do the work of accessing the encapsulated string value.

5

As with all C++ Subject implementations, we must override the method _this() in order to return the correct type. This is critical for proper use of the specific Subject type.

6

Here we have the string value that will be encapsulated within the servant instance. For convenience, we use the std::string type, though it will require careful handling of the char* data that CORBA actually passes around.

7

In this Subject implementation, we are being more paranoid about concurrent accesses to mValue, so we will protect it with a mutex.

Next, we look at the very simple implementations of CxxClientTest::StringSubjectImpl::setValue() and CxxClientTest::StringSubjectImpl::getValue(). These are found in the file StringSubjectImpl.cpp, and the code is shown in Example 7.3, “StringSubjectImpl.cpp”. It is important to note the use of guards in the method implementations. These provide an exception-safe mechanism for controlling access to the data member mValue. When constructed, the guard locks the mutex passed as the argument to the constructor. When the guard goes out of scope, the mutex is automatically unlocked.

Example 7.3. StringSubjectImpl.cpp

  1 #include <vpr/Sync/Guard.h>
    #include <StringSubjectImpl.h>                              1
    
    namespace CxxClientTest
  5 {
    
    void StringSubjectImpl::setValue(const char* value)
    {
       {
 10       vpr::Guard<vpr::Mutex> val_guard(mValueLock);         2
          mValue = std::string(value);                          3
       }
    
       // Notify any observers that our value has changed.  This is very
 15    // important.
       tweek::SubjectImpl::notify();                            4
    }
    
    char* StringSubjectImpl::getValue()
 20 {
       vpr::Guard<vpr::Mutex> val_guard(mValueLock);            5
       return CORBA::string_dup(mValue.c_str());                6
    }
    
 25 } // End CxxClientTest namespace
1

As usual, we must include our header file to get the necessary declarations.

2 3

Within this scoped block, we lock the mValueLock using a guard and assign a new value to mValue. Upon exiting this block, the guard goes out of scope, and mValueLock is unlocked.

4

As with all cases where we modify the state of the Subject, we must notify any Observers who are attached to us. Just as the comment notes, this is very important.

5 6

Here, we return the current value of mValue. We use a guard again to simplify unlocking the mutex (it happens automatically upon return). Note that we make a copy of the memory contained in mValue using CORBA::string_dup(). Making a copy is required by CORBA.

Observer Implementation

With the Subject implemented, we now turn our attention to a C++ Observer implementation. Its implementation we will be very simple. Its update() method will query the current string value and print it to the console. The goal here is to demonstrate how to write a C++ Observer, not how to write an interesting Observer.

Example 7.4. StringObserverImpl.h

  1 #ifndef _STRING_OBSERVER_IMPL_H_
    #define _STRING_OBSERVER_IMPL_H_
    
    #include <tweek/CORBA/Observer.h>                        1
  5 #include <StringSubject.h>                               2
    
    class StringObserverImpl : public POA_tweek::Observer    3
    {
    public:
 10    StringObserverImpl(CxxClientTest::StringSubject_var subject)
          : mSubject(subject)
       {
          /* Do nothing. */ ;
       }
 15 
       virtual ~StringObserverImpl()
       {
          /* Do nothing. */ ;
       }
 20 
       virtual void update();                                4
    
       void detach()
       {
 25       mSubject->detach(_this());                         5
       }
    
    private:
       CxxClientTest::StringSubject_var mSubject;            6
 30 };
    
    #endif /* _STRING_OBSERVER_IMPL_H_ */
1 2

First, we include the headers we need. The first declares the basic tweek::Observer C++ interface. The second includes the tweek::StringSubject interface. We need this header so that we can hang onto a reference to our Subject.

3

Our Observer implementation derives from the basic Tweek Observer class POA_tweek::Observer. The Java equivalent is tweek.ObserverPOA. Refer to Example 6.4, “SliderObserverImpl.java” in the section called “Implementing the Observer in Java” to see the correspondence.

4

The update() method must be implemented for all subclasses of POA_tweek::Observer. Here, we just declare the method; the implementation will be shown in Example 7.5, “StringObserverImpl.cpp”.

5

The detach() method is provided as an easy way to detach the Observer from its Subject. Ideally, this would be done in the destructor so that the detaching process happens automatically when the Observer servant is deleted. Doing so leads to incorrect deactivation of the servant, however. Instead, we provide this method as a way to perform the detachment before servant deactivation or deletion occurs.

6

Finally, we have a member variable that we will use to retain a reference our Subject. This allows the Observer to be passed around to functions without passing its Subject explicitly. Further, we will see that having a reference to the Subject is required for the update() method implementation to work.

Now, we show the implementation of StringObserverImpl::update(). This method is implemented in StringObserverImpl.cpp. While the method body is very short, we use the .cpp file to encourage the implementation of methods outside of the class declaration.

Example 7.5. StringObserverImpl.cpp

#include <iostream>
#include <StringObserverImpl.h>                     1

void StringObserverImpl::update()
{
   char* cur_value = mSubject->getValue();          2
   std::cout << "Current string value is now '"     3
             << cur_value << "'" << std::endl;
   delete cur_value;                                4
}
1

As usual, we have to include our header file to get the class declaration information.

2 3

Since update() is only called when the state of the Subject changes, we need to query its current state. Once we get the current string value back, we print it to the console. The value is enclosed in single quotes so that any leading or trailing whitespace is displayed clearly.

4

Lastly, we delete the memory returned by CxxClientTest::StringSubject::getValue(). The burden for freeing this memory is on the client, as per standard CORBA operating procedure.

Client Application

Finally, we look at the C++ client application. This brings everything together and makes use of the tweek::CorbaService class to contact the remote server. The application shown below is more complicated than the server because more work must done. We must initialize the local CORBA Service (the local ORB); we must pick out the correct Subject Manager reference; and we must get the correct CxxClientTest::StringSubject reference from the Subject Manager. Once all of those steps are completed, we can create an Observer servant (an instance of StringObserverImpl) and attach it to the remote Subject.

The file containing the complete client application source is client.cpp. We will examine it in three parts: the required headers, the implementation of main(), and the Subject Manager lookup.

Example 7.6. client.cpp: Required Header Files

#include <iostream>
#include <string>

#include <vpr/vpr.h>
#include <vpr/Util/Debug.h>
#include <tweek/Client/CorbaService.h>    1
#include <tweek/CORBA/SubjectManager.h>   2

#include <StringObserverImpl.h>           3
1

The key to the C++ client interface is the CORBA Service. Its declaration is found in tweek/Client/CorbaService.h.

2

As in Java clients, the Subject Manager plays a vital role in getting references to remote Subjects. Note that the file we include here is generated by the IDL compiler and provides only the basic interface. We will not be using the actual Subject Manager C++ implementation here. That is only used on the server side.

3

Lastly, we include the implementation of our Observer. We will be creating an instance of this class to act as the servant for an Observer reference. The servant we create will be attached to a remote Subject, just as in all Java clients.

Next, we look at the implementation of the application's main() function. This is where the bulk of the work is done. Of course, in real-world use, modularizing the code would be much better than dumping most of it in main(). For the purposes of this example, we can get by with having most of the code in main().

Note the use of try/catch blocks in the client application code. As in the case of server applications (refer to the section called “The Server Application”), we are careful about handling exceptions properly. Remember that CORBA uses exceptions extensively to indicate errors, and thus it is necessary for user code to catch them.

Example 7.7. client.cpp: Implementation of main(), Part I

  1 int main(int argc, char* argv[])
    {
       std::string ns_host, iiop_ver;
       vpr::Uint16 ns_port;
  5 
       std::cout << "Naming Service host: ";                                  1
       std::cin >> ns_host;
    
       std::cout << "Naming Service port (usually 2809): ";                   2
 10    std::cin >> ns_port;
    
       std::cout << "IIOP version (usually 1.0): ";                           3
       std::cin >> iiop_ver;
    
 15    // Create the local CORBA Service using the Naming Service URI information
       // we just collected.
       tweek::CorbaService corba_service(ns_host, ns_port, iiop_ver);         4
    
       try
 20    {
          // Attempt to initialize the CORBA Service.
          if ( corba_service.init(argc, argv).success() )                     5
          {
             // This will hold the reference to the Subject Manager we use.
 25          tweek::SubjectManager_var subj_mgr =
                chooseSubjectManager(corba_service);                          6
    
             // Verify that we actually got a Subject Manager reference back
             // from chooseSubjectManager.
 30          if ( ! CORBA::is_nil(subj_mgr) )                                 7
             {
                // Request the Subject with which we will communicate.  This
                // hard-coded Subject name is not necessarily a good thing.
                tweek::Subject_var subj =
 35                subj_mgr->getSubject("StringSubject");                     8
    
                // If the Subject Manager knows about the Subject named above,
                // then we are good to go.
                if ( ! CORBA::is_nil(subj) )                                  9
 40             {
                   ...  // Shown in the next example block
                }
             }
             // We did not get a Subject Manager reference back for some reason.
 45          else
             {
                vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
                   << "No Subject Manager chosen--exiting.\n" << vprDEBUG_FLUSH;
             }
 50       }
          // The CORBA Service initialization failed.
          else
          {
             vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
 55             << "CORBA Service failed to initialize\n" << vprDEBUG_FLUSH;
          }
       }
       catch (...)
       {
 60       vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
             << "Caught an unknown exception!\n" << vprDEBUG_FLUSH;
       }
    
       return 0;
 65 }
1 2 3

Here, we query input from the user to get Naming Service information. We need the host name where the Naming Service is running, the port on which it is listening, and the version of IIOP to use. Using the values we get from the user, we can construct an instance of tweek::CorbaService. Internally, it will use the values to create a URI for looking up the Naming Service reference.

4

Once we have all the necessary initialization pieces, we can construct an instance of tweek::CorbaService. Simply creating an instance of this class does not perform any CORBA-related activities. That is the next step.

5

Now we initialize the local CORBA Service. We pass in argc and argv in case the user provided any CORBA-specific command-line parameters. (Any such parameters will be stripped from argv, and argc will be decremented accordingly.) If initialization succeeds, we proceed to requesting the Subject Manager reference.

6 7

Using the CORBA Service, we choose the Subject Manager through which all Subject requests will be handled. The work for making this choice is offloaded to the helper function chooseSubjectManager(), shown in Example 7.9, “client.cpp: Implementation of chooseSubjectManager()”. For now, we just assume that we got back some reference, possibly nil, to a remote tweek::SubjectManager object. Since we do not know for sure what the state of things is on the server side or what decision the user made, we verify that we did not get back a nil reference. If we got back a valid Subject Manager reference, we can use it just as we would in a Java client to request Subject references by name.

8 9

Next, we request the reference to the remote Subject. The symbolic name we use here is the same as that specified in the server application (not shown in this chapter). Note that using a hard-coded name in this way is not recommended, but we use it here for the sake of simplicity. Once we have a reference, we verify that it is not nil before trying to use it.

We now narrow our attention to the handling of the Subject reference that was returned by the Subject Manager. The code shown in Example 7.8, “client.cpp: Implementation of main(), Part II” comes from the “...” block in Example 7.7, “client.cpp: Implementation of main(), Part I”. At this point in the application execution, we know that we have a non-nil tweek::Subject reference, so we need to narrow it to our specific type, create an Observer servant, and attach it to the remote Subject.

Example 7.8. client.cpp: Implementation of main(), Part II

  1 StringObserverImpl* string_observer(NULL);
    PortableServer::ObjectId_var observer_id;
    
    try
  5 {
       // Attempt to narrow subj to the more specific reference type
       // CxxClientTest::StringSubject_var.  If this fails, an
       // exception will be thrown and caught below.
       CxxClientTest::StringSubject_var string_subj =
 10       CxxClientTest::StringSubject::_narrow(subj);               1
    
       // Request the current value before we create the Observer.
       // In this way, we can see the persistent state maintained
       // by the Subject.
 15    char* cur_value = string_subj->getValue();
       std::cout << "Current string value is '" << cur_value << "'"
                 << std::endl;
       delete cur_value;
    
 20    // Create our Observer servant.
       string_observer = new StringObserverImpl(string_subj);        2
    
       // Register the newly created servant with our ORB's POA.
       observer_id =
 25       corba_service.registerObject(string_observer,
                                       "StringObserver");            3
    
       // This could be done in the StringObserverImpl constructor,
       // but we do it here in this example just to make it clear
 30    // that it is important.
       string_subj->attach(string_observer->_this());                4
    
       const std::string exit_string("Q");
       std::string cur_string;
 35 
       for ( ;; )                                                    5
       {
          std::cout << "Enter a string (Q to quit): ";
          std::cin >> cur_string;
 40 
          if ( exit_string != cur_string )
          {
             string_subj->setValue(cur_string.c_str());              6
          }
 45       else
          {
             break;
          }
       }
 50 }
    catch (...)
    {
       vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
          << "Caught an unknown exception during object interaction!\n"
 55       << vprDEBUG_FLUSH;
    }
    
    // We're done, so now we have to clean up after ourselves.
    // The order of operations here is important.
 60 if ( NULL != string_observer )
    {
       string_observer->detach();                                    7
       corba_service.unregisterObject(observer_id);                  8
       delete string_observer;                                       9
 65 }
1

Given a non-nil Subject reference, we now need to narrow it to our specific Subject type, CxxClientTest::StringSubject. If the narrowing fails, an exception will be thrown. In that case, we are done because we did not get back a reference we can use.

2

If the narrowing succeeded, then we can create an Observer servant (an instance of our class StringObserverImpl). In our local ORB, this servant will handle accesses from remote objects—namely, the Subject to which it is attached.

3

Next, we register our newly created Observer servant with the POA in the local CORBA Service. We give it an arbitrary name, and it gives us a unique identifier for the servant. We will need this identifier later when the application is shutting down and cleaning up after itself.

4

At long last, we can attach our Observer to the remote Subject. The mechanism for doing this is nearly identical to the Java version. The only difference is the use of the -> operator, which is not present in Java. Once the Subject knows about our Observer, it will be informed of all state changes.

5

In this loop, we ask the user for input and then pass that input to the Subject's setValue() method. We do this until the user enters the string "Q", which denotes that s/he wants to quit the application.

6

This is where we modify the state of the remote Subject. We pass the C string version of the user-specified string as the argument to CxxClientTest::StringSubject::setValue().

7 8 9

Once the user has requested to quit the application, we need to clean up the servant we constructed earlier. First, we detach the Observer from the remote Subject. Next, we unregister our servant using the ID returned by the local CORBA Service. Finally, we delete the heap memory allocated for the servant. With that, we are done.

The last part of the client application is the choice of the Subject Manager reference to use. In this example, we put that code in the helper function chooseSubjectManager(). In this function, we request the list of valid Subject Manager references from the local CORBA Service and present the information about each one to the user. Using this information, the user selects one, and that reference is then returned to the caller. In a real-world example, the Subject Manager chooser would be much more sophisticated than what we show in Example 7.9, “client.cpp: Implementation of chooseSubjectManager()”, but for the purposes of explaining the ideas, this will suffice.

Example 7.9. client.cpp: Implementation of chooseSubjectManager()

  1 tweek::SubjectManager_var chooseSubjectManager(tweek::CorbaService& corbaService)
    {
       tweek::SubjectManager_var subj_mgr;
    
  5    // Request all the active Subject Manager references.
       std::list<tweek::SubjectManager_var> mgrs =
          corbaService.getSubjectManagerList();                                   1
    
       std::list<tweek::SubjectManager_var>::iterator cur_mgr;
 10 
       // Iterate over all the tweek::SubjectManager references we have.
       for ( cur_mgr = mgrs.begin(); cur_mgr != mgrs.end(); ++cur_mgr )           2
       {
          try
 15       {
             // It is not entirely safe to assume that *cur_mgr is still valid at
             // this point, even though it was valid when the mgrs list was
             // constructed.  Hence, we test it again now.
             if ( ! (*cur_mgr)->_non_existent() )                                 3
 20          {
                std::string response;
                const std::string proceed("y");
    
                tweek::SubjectManager::SubjectManagerInfoList_var mgr_info =      4
 25                (*cur_mgr)->getInfo();
    
                std::cout << "\nSubject Manager information:" << std::endl;
    
                // Loop over the information items and print each key/value pair
 30             // to the screen.
                for ( CORBA::ULong i = 0; i < mgr_info->length(); ++i )           5
                {
                   std::cout << "\t" << mgr_info[i].key  << " = "
                             << mgr_info[i].value << std::endl;
 35             }
    
                std::cout << "Use this Subject Manager (y/n)? ";
                std::cin >> response;
    
 40             if ( proceed == response )
                {
                   subj_mgr = *cur_mgr;                                           6
                   break;
                }
 45          }
          }
          catch (...)
          {
             vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
 50             << "Caught an unknown exception in chooseSubjectManager loop\n"
                << vprDEBUG_FLUSH;
          }
       }
    
 55    return subj_mgr;
    }
1

Using the given tweek::CorbaService reference, we ask for the current list of valid tweek::SubjectManager references. The references in this list are guaranteed to be valid at the time the list was constructed.

2

We use an STL iterator to loop over the list of returned tweek::SubjectManager references. This loop presents each reference in sequence and asks the user if the current reference is the one s/he wants. The loop completes when the user selects a reference or when no more references are available.

3

While the list of Subject Manager references was guaranteed to have contained valid references when it was constructed, things may have changed since then. That is the nature of asynchronous programming. To be safe, we test the current Subject Manager reference to see if it still refers to an extant object. If so, we continue. If not, we skip it. This invocation of tweek::SubjectManager::_non_existent() may throw an exception, and for that reason, we enclose the body of the for loop in a try/catch block.

4 5

It is possible for a single Naming Service to have multiple active Subject Manager references. Each of these has a unique identifier within the Naming Service, but the identifier is not human readable. To work around this, we make use of the method tweek::SubjectManager::getInfo(). This method returns a sequence of key/value pairs (both are strings) that can be used to identify which Subject Manager reference is the correct one. Here, we request this information sequence and print all the key/value pairs. With this output, the user can (hopefully) determine which Subject Manager reference to use.

Note

This example is purposefully simple to keep the code small. Readers are strongly encouraged to come up with much more sophisticated choosing mechanisms here. For example, the flexibility of the Subject Manager information sequence would allow choices to be made entirely in code without any interactive user feedback.

6

Here, the user has selected the current Subject Manager as the correct one. We copy the reference to the subj_mgr variable. Then, we break out of the loop and return subj_mgr to the caller.

With that, we are done with our review of the C++ client API in Tweek. The use of CORBA allows Java and C++ client code to be quite similar, and this can be helpful when migrating between the two. The addition of the C++ client API in Tweek 0.13 also demonstrates one of the basic design philosophies of Tweek: clients can be written in any language without concern for the language the server uses.