Rogue Wave banner
Previous fileTop of DocumentContentsIndexNext file

5.3 Exchanging Objects

In that last section we saw how to pass basic types between C++ and Java. Now let's look at exchanging objects. The key component for exchanging objects between a C++ program and a Java program is a Java streamer class. A streamer is any class that implements the ObjectStreamer interface from package com.roguewave.vsj. Each streamer represents a mapping between a C++ class and a Java class. When reading objects, the streamer is responsible for decoding the information representing the C++ object written from the C++ program and creating an instance of the Java class. When writing objects, the streamer is responsible for taking a Java object and writing its representation on the stream in the format expected by the C++ program.

5.3.1 Prebuilt Streamers

Prebuilt streamers are provided in package com.roguewave.vsj.streamer as public members of the following classes:

5.3.2 Required Components

An application that exchanges objects between a C++ program and a Java program must have the following components:

5.3.3 Example

Let's look at an example that writes out a Tools.h++ RWTime from a C++ program, and reads it into a Java program as a java.util.Date. Like most Tools.h++ classes, RWTime objects may be saved and restored via virtual streams. The Java streamer we use, SimpleMappings.Time, is one of the prebuilt streamers provided by package com.roguewave.vsj.streamer.

The C++ program

The Java program

Here's a transcript of running the above example at approximately 11:17 PM on 25 Feb 1997:

5.3.4 ObjectStreamers

The Java virtual streams have two methods for reading and writing objects: VirtualInputStream.restoreObject(ObjectStreamer) and VirtualOutputStream.saveObject(Object, ObjectStreamer) respectively. It is the job of the ObjectStreamer to effect the mapping between the C++ and Java object. As noted earlier, we aren't really putting objects into the stream; we are putting information from which a corresponding object can be constructed.

There are two sorts of information one might find in the stream. First, there is information representing the internal state of an object; for example, the salary in an Employee object. Second, there is information about the object, or meta-information. In Tools.h++ we have two examples of meta-information:

The ObjectStreamer must understand both sorts of information well enough to serialize and de-serialize Java objects that are compatible with the information expected on the C++ side, or provided from it. But before we get too deep, let's look at a simple streamer that deals only with the first type of information, the state of an object.

5.3.5 Simple Streamers

Simple streamers are so called because they map classes that do not include meta-information as part of their serialized representations. We've already seen how to use a simple streamer in the example above with SimpleMappings.Time. Creating one is conceptually very simple: implement the ObjectStreamer interface and provide the two member functions restore() and save().

Let's create a streamer that maps a C++ Point struct to a Java Point class. If we know that the format of a C++ Point in a virtual stream is an int followed by an int, we can easily write the following Java Point class and streamer:

C++ Program

Java Program

Using this streamer with the Java virtual stream methods saveObject() and restoreObject() allows you to exchange Point objects between C++ and Java programs, as in the RWTime example in Section 5.3.3.

5.3.6 Refresher: Persistence in Tools.h++

While simple streamers are useful, the more powerful forms of serialization make use of information beyond an object's state. These objects require more complex streamers and, although the jtools components protect you from much of this added complexity, it helps to look more closely at the different levels of persistence found in Tools.h++. In the Tools.h++ Class Reference, every class whose instances can be saved to or restored from a virtual stream contains a persistence section that gives one of three levels of persistence relating to the types of meta-information above.

Of course you aren't limited to saving instances of Tools.h++ classes to virtual streams; you can save instances of your own classes as well. Each level of serialization demands different requirements of you, as shown in Table 9.

Table 9 -- Persistence in Tools.h++

 
LevelUse Tools.h++ Macro...Implement in C++...
Simple
none
Member or global functions to read and write the state of your object
Isomorphic
RWDEFINE_PERSISTABLE
Global functions to read and write the state of your object
Polymorphic
RWDEFINE_COLLECTABLE
Derive from class RWCollectable. Provide a unique Class Id (or String Id). Member functions to read and write the state of your object

5.3.7 Complex Streamers

In Tools.h++, meta-information such as back references and class Ids is handled behind the scenes by the library via inheritance and/or macros. You are responsible only for reading and writing the state of your objects. The same is true in this Java framework. The packages include the following prebuilt streamers that can decode and encode back references and class Ids:

These streamers know only about the meta-information, and nothing about the actual classes they might encounter when streaming. Each has a corresponding interface that you use to provide the specific information about your class. For class PersistableStreamer you must supply a class that implements the DefinePersistable interface, while for class CollectableStreamer you must provide one or more classes that implement the DefineCollectable interface.

When implementing either the DefinePersistable or DefineCollectable interface, please note an important restriction regarding your implementation of the create(VirtualInputStream vstr) member function. Although the PersistableStreamer provides an input stream when calling your create() function, you must not do anything that would result in a call to vstr.restoreObject(). The PersistableStreamer keeps track of the ordinal position of each object in the stream and a recursive call to vstr.restoreObject() during create() causes these positions to be recorded incorrectly. The results could be catastrophic.

You may, however, use the provided input stream to read built-in types such as int or double if they are necessary for the creation of your object. For example, since the value of a java.lang.Integer can be set only at construction time, the following create() function must read an int from the input stream:

You may freely make recursive calls to vstr.restoreObject() from your restoreGuts() function.

5.3.8 Class PersistableStreamer and the DefinePersistable Interface

Class PersistableStreamer must be used for any Tools.h++ class marked Isomorphic in the persistence section of its class reference entry, or for any of your own classes made persistent through the macro RWDEFINE_PERSISTABLE.

PersistableStreamer needs to know how to read and write the state of the object being saved or restored. This information is provided in the PersistableStreamer constructor. Specifically, you must provide an object whose class implements the DefinePersistable interface. This interface includes a method create(), for creating an instance of the object being restored, and methods saveGuts() and restoreGuts(), for writing and reading the state of the object.

The next example, called the Employee example, describes a C++ program that saves a templatized doubly-linked list, an RWTPtrDlist<Employee> of Employee objects, with a Java program that reads in the list as a com.roguewave.tools.v2-0.Dlist. Since this code example is quite long, only relevant portions are quoted below.


NOTE: Complete code for this example is located in the examples directories created for your installation of Tools.h++ Professional. The "Examples" chapter in Part V, "Resources," describes the locations of those directories, or you can check the online build guide for your installation media.

In both the C++ and Java programs, the Employee classes contain an employee's name, manager, and department.

5.3.8.1 From the C++ Program (empls_out.cpp)

5.3.8.2 From the Java Program (Employee.java)

Below we show how the Employee class in the C++ program is made persistable via the macro RWDEFINE_PERSISTABLE. The rwSaveGuts() and rwRestoreGuts() methods are implemented to save and restore the contents of an Employee object.

5.3.8.3 From the C++ Program (empls_out.cpp)

Since the C++ Employee class in the code above was made persistable via the macro RWDEFINE_PERSISTABLE, the Java program uses a PersistableStreamer. The PersistableStreamer is constructed with an instance of DefinePersistableEmployee, a class that implements the DefinePersistable interface to save and restore the state of the Java Employee objects.

5.3.8.4 From the Java Program (DefinePersistableEmployee.java)

For the mapping of an RWTPtrDlist<T> to a com.roguewave.tools.v2-0.Dlist, we use one of the streamers provided by class com.roguewave.vsj.streamer.TemplateMappings. Note that we don't do anything that would result in a call to restoreObject() from the create() method. Note also that the Employee objects point to one another in their manager fields, and that the graph of these objects in maintained, as shown in the sample output.

5.3.8.5 From the C++ Program (empls_out.cpp)

5.3.8.6 From the Java Program (empls_in.java)

The output of the complete programs looks like this:

5.3.9 Class CollectableStreamer and the DefineCollectable Interface

In the previous section, we saw how you must use class PersistableStreamer for isomorphic Tools.h++ classes. Analogously, you must use class CollectableStreamer for any Tools.h++ class marked as Polymorphic in the persistence section of its class reference entry, or for any of your own classes that were made serializable through the macros RWDEFINE_COLLECTABLE or RWDEFINE_NAMED_COLLECTABLE.

An advantage of polymorphic streaming is that you don't have to know the exact type of the objects being streamed. However, you do have to know all the types of objects that might be streamed because, at some point, there must be code that knows how to read and write the states of those objects.

CollectableStreamer accomplishes polymorphic streaming through a registration process. After you create a CollectableStreamer, you use its register() method to notify it of each class that might be streamed. Specifically, you must register objects whose classes implement the DefineCollectable interface.

The DefineCollectable interface interface, like DefinePersistable, includes the create(), saveGuts(), and restoreGuts() methods. It also includes methods that return the C++ class Id as supplied to the macro RWDEFINE_COLLECTABLE, or the C++ string Id as supplied to the macro RWDEFINE_NAMED_COLLECTABLE. Still another method returns the java.lang.Class object for the corresponding Java class. These methods are used during registration by the CollectableStreamer so that later it can automatically call the right DefineCollectable object based on the run-time type of the object being saved or restored.

While the Tools.h++ RWCollectable class provides a compareTo() member function for ordering elements, java.lang.Object does not. To make up for this discrepancy, the DefineCollectable interface includes a method that returns an object com.roguewave.tools.v2-0.Comparator. For classes that don't have meaningful comparison semantics, you can return an instance of com.roguewave.vsj.streamer.CollectableCompare. In Tools.h++, using CollectableCompare is somewhat equivalent to not overriding RWCollectable::compareTo() when you derive your own class from RWCollectable.

This next example, called the Bus example, writes a Bus object from a Java program and reads it into a C++ program. Again, since this code example is quite long, only relevant portions are quoted below.


NOTE: Complete code for this example is located in the examples directory created for your installation of Tools.h++ Professional. The "Examples" chapter in Part V, "Resources," describes the location of that directory, or you can check the online build guide for your installation media.

The Java Bus object contains an int, a String, and two Sets of Strings. On the C++ side, the Bus class derives from RWCollectable and uses the macro RWDEFINE_COLLECTABLE for persistence.

5.3.9.1 From the Java Program (Bus.java)

5.3.9.2 From the C++ Program (bus.h)

Below, we use a CollectableStreamer and register with it an object whose class implements the DefineCollectable interface specifically for Bus objects. The sample code also takes advantage of some of the provided mappings in com.roguewave.vsj.streamer that map a java.lang.String to a Tools.h++ RWCString, a java.lang.String to a Tools.h++ RWCollectableString, and a com.roguewave.tools.v2-0.Set to a Tools.h++ RWSet.

5.3.9.3 From the Java Program (bus_out.java)

Remember that you must not do anything that would result in a call to restoreObject() from your create() method.

5.3.9.4 The C++ Program (bus_in.cpp)

Finally, it is a simple matter to serialize and restore the Bus object. On the Java side, we instantiate a Bus object, and add passengers and customers to it. Then we open a PortableOutputStream, register a new DefineCollectableBus, and save the Bus to the stream.

5.3.9.5 From the Java Program (bus_out.java)

5.3.9.6 From The C++ Program (bus_in.cpp)

Here's what the output looks like when you run the complete Bus example:

The addresses above are given to show that the graph of objects has been maintained; for example, the two Johns are the same object.



Previous fileTop of DocumentContentsIndexNext file

©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.