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.
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
{
void setValue(in string val);
string getValue();
};
};
#endif /* _CXX_CLIENT_TEST_STRING_SUBJECT_IDL_ */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>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,
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);
40 /** * Returns this subject's internal value. */ virtual char* getValue();
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()
{ return POA_CxxClientTest::StringSubject::_this(); } 55 private: std::string mValue; /**< Our value */
vpr::Mutex mValueLock; /**< A mutex to protect mValue accesses */
}; 60 } // End of CxxClientTest namespace #endif /* _STRING_SUBJECT_IMPL_H_ */
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>namespace CxxClientTest 5 { void StringSubjectImpl::setValue(const char* value) { { 10 vpr::Guard<vpr::Mutex> val_guard(mValueLock);
mValue = std::string(value);
} // Notify any observers that our value has changed. This is very 15 // important. tweek::SubjectImpl::notify();
} char* StringSubjectImpl::getValue() 20 { vpr::Guard<vpr::Mutex> val_guard(mValueLock);
return CORBA::string_dup(mValue.c_str());
} 25 } // End CxxClientTest namespace
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>5 #include <StringSubject.h>
class StringObserverImpl : public POA_tweek::Observer
{ public: 10 StringObserverImpl(CxxClientTest::StringSubject_var subject) : mSubject(subject) { /* Do nothing. */ ; } 15 virtual ~StringObserverImpl() { /* Do nothing. */ ; } 20 virtual void update();
void detach() { 25 mSubject->detach(_this());
} private: CxxClientTest::StringSubject_var mSubject;
30 }; #endif /* _STRING_OBSERVER_IMPL_H_ */
| 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. |
| 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. |
| 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”. |
| 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. |
| 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>void StringObserverImpl::update() { char* cur_value = mSubject->getValue();
std::cout << "Current string value is now '"
<< cur_value << "'" << std::endl; delete cur_value;
}
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>#include <tweek/CORBA/SubjectManager.h>
#include <StringObserverImpl.h>
![]()
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: ";
std::cin >> ns_host;
std::cout << "Naming Service port (usually 2809): ";
10 std::cin >> ns_port;
std::cout << "IIOP version (usually 1.0): ";
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);
try
20 {
// Attempt to initialize the CORBA Service.
if ( corba_service.init(argc, argv).success() )
{
// This will hold the reference to the Subject Manager we use.
25 tweek::SubjectManager_var subj_mgr =
chooseSubjectManager(corba_service);
// Verify that we actually got a Subject Manager reference back
// from chooseSubjectManager.
30 if ( ! CORBA::is_nil(subj_mgr) )
{
// 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");
// If the Subject Manager knows about the Subject named above,
// then we are good to go.
if ( ! CORBA::is_nil(subj) )
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 } | 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. |
| 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. |
| 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. |
| 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. |
| 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);
// 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);
// Register the newly created servant with our ORB's POA.
observer_id =
25 corba_service.registerObject(string_observer,
"StringObserver");
// 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());
const std::string exit_string("Q");
std::string cur_string;
35
for ( ;; )
{
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());
}
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();
corba_service.unregisterObject(observer_id);
delete string_observer;
65 }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();
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 )
{
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() )
20 {
std::string response;
const std::string proceed("y");
tweek::SubjectManager::SubjectManagerInfoList_var mgr_info =
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 )
{
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;
break;
}
45 }
}
catch (...)
{
vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL)
50 << "Caught an unknown exception in chooseSubjectManager loop\n"
<< vprDEBUG_FLUSH;
}
}
55 return subj_mgr;
}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.