
This discussion is based on the shipped example java-interop, located in your <installdir>\tutorials\sdo directory.
This example illustrates how to access a C++ application's data in shared memory from a Java application.
This tutorial illustrates how to write a class in Java that parses a DataGraph to shared memory, and pass that data back and forth between a C++ application. This includes developing a Java class that declares a native method implemented in C++.
Here is the procedure in more detail:
The Java class Interop.java creates a DataObject that contains two integers, a and b. It wraps this DataObject into a DataGraph.
Interop.java then calls to another Java class containing a native method calculateTotal() implemented in a C++ function. This method performs a calculation on the data. Here's how this works:
a. | The Interop.calculateTotal() method calls another Java application, InteropCpp.java, which declares the native method. This class uses the JNI method loadLibrary() to access the implementation of calculateTotal() on the C++ side. |
b. | InteropCpp.java then invokes the native method, which calls InteropCpp.cpp through its interface InteropCpp.h. This is the C++ function that contains the implementation of calculateTotal(). |
The C++ file InteropCpp.cpp performs the calculation and passes the data back to the Java application Interop.java.
Interop.java prints the changed values to illustrate the successful cross-platform data exchange.
Before discussing this particular tutorial in more detail, it may be useful to understand how to use JNI in your own applications.
The general steps for creating a Java class that can access the functionality in a C++ class are:
Create a Java class with a native method, and compile it. This is discussed in Section 6.4.3.
Use the JDK javah tool to generate a C++ header file based on the native method declared in Step 1. The generated header file will include all necessary imports. (For this tutorial, this header file is generated as part of running the Ant "build" target.)
Write the C++ code implementing the method, compile it, and link it into a DLL.
Load the DLL from your Java program, and execute your program.
The tutorial begins with a simple Java class Interop.java that creates and instantiates a DataObject containing two integers. The code is regular Java (non-JNI) written to the SDO specification featuring API usage already discussed in other tutorials. For this reason, we'll look briefly at this code, and then more closely at the class that declares the native method. For more detail on creating DataObjects and Types, see any tutorial in your <installdir>\tutorials\sdo\java or sdo\cpp directory.
First, we'll create a new Type on which we'll base our DataObject:
DataObject sumType = DataFactory.INSTANCE.create( "commonj.sdo", "Type" );
sumType.set( "uri", DOC_URI ); //1
sumType.set( "name", "sum" );
DataObject propA = sumType.createDataObject( "property" );
propA.set( "name", "a" );
propA.set( "type", TypeHelper.INSTANCE.getType( "commonj.sdo", "Int" ) );
DataObject propB = sumType.createDataObject( "property" );
propB.set( "name", "b" );
propB.set( "type", TypeHelper.INSTANCE.getType( "commonj.sdo", "Int" ) );
TypeHelper.INSTANCE.define( sumType );
| //1 | Create a new Type called sumType and add two Properties, a and b, both of Type Int. |
Now we'll create a DataObject based on our new Type and populate its Properties with some values.
DataObject root = DataFactory.INSTANCE.create( "commonj.sdo", //1
"DataObject" );
final int a = 84, b = 48; //2
log("Creating DataObject 'sum'");
final DataObject sum = root.createDataObject("sum", DOC_URI, "sum"); //3
log("Setting values of 'a' and 'b' to " + a + " and " + b );
sum.setInt("a", a); //4
sum.setInt("b", b);
XMLDataAccessService xmlDas = XMLDataAccessServiceFactory.create(); //5
DataGraph graph = xmlDas.createGraph(root, DOC_URI, "root");
| //1 | Create DataObject instance root. |
| //2 | Initialize two integers a and b, and set their values. |
| //3 | Create a new DataObject sum from the root DataObject, based on the new Type. |
| //4 | Set the sum object's Properties a and b to the values of the previously created integers a and b, and log some activity. |
| //5 | Use the XML DAS to create a DataGraph graph containing the DataObject sum. |
Next, we'll make a call to some JNI code that will in turn invoke the C++ implementation to perform the calculation.
log("Calling C++ code");
InteropCpp.calculateTotal( graph ); //1
final int total = sum.getInt("aplusb"); //2
log("Value in 'aplusb' is " + total );
}
private static void log(String msg) {
System.out.println( "[Java] " + msg );
System.out.flush();
}
}
| //1 | Call the Java class InteropCpp that contains the JNI method calculateTotal() and pass it the DataGraph. Here's where the logic is preparing to be passed off to the C++ side. |
| //2 | When the answer is ready, the C++ application returns it as an integer aplusb, which the DataObject sum can retrieve using the simple generic getInt() method. |
Note that there is nothing special about this class. It simply creates a DataObject, populates it with some values, wraps it in a DataGraph, and calls a method in another Java class, InteropCpp.java, which actually declares the native method. Let's look at InteropCpp.java.
The class InteropCpp.java declares the JNI method and calls into the C++ code.
import commonj.sdo.DataGraph; //1
import com.roguewave.rwsf.sdo.xml.api.NativeDataAccessService;
import com.roguewave.rwsf.sdo.xml.api.NativeDataAccessServiceFactory;
public class InteropCpp {
static {
System.loadLibrary("InteropCpp"); //2
}
public static void calculateTotal(DataGraph dataGraph) { //3
NativeDataAccessService ndas = NativeDataAccessServiceFactory.create(); //4
int handle = ndas.getHandle(dataGraph);
calculateTotal(handle); //5
}
private static native void calculateTotal(int handle); //6
}
| //1 | Include the Native DAS classes required to access C++ applications from Java. |
| //2 | Loads the C++ library containing the JNI method's implementation into a DLL for use by the VM. Note: The directory containing the DLL must be included in your java.library.path. |
| //3 | The method calculateTotal() takes as an argument the DataGraph passed to it by the class Interop.java. |
| //4 | Creates a Native DAS and passes it the handle representing the DataGraph. |
| //5 | Call the JNI method declared in //6, passing the DataGraph handle. |
| //6 | Here finally is the declaration of the JNI native method that is implemented in C++. Note the native keyword, which tells the Java compiler that this method is implemented in native code and will be loaded into the VM at runtime. |
Now that the native method has been declared in Java, you can implement it in C++. This requires two steps:
Generate a C++ header file (with a .h extension) based on your declared native method in Java. This file will contain the necessary function signature in C++. The JDK provides a utility, javah that generates C/C++ header files with the necessary function signature.
Write C++ code to implement the function.
For convenience, this tutorial includes the implemented C++ function based on a previously-generated header file. This header is not provided as part of this tutorial; rather, it is automatically generated when you compile the Java code.
You do not need to generate this header yourself, or even look at it. However, if you need to use the procedure described here to call C++ code from Java, you will need to perform this step and you may wish to understand the contents of the generated code. Section 6.4.4.1 includes a simple introduction of the procedure.
If you wish to move on to implementing the C++ function, see Section 6.4.4.2.
The JDK tool javah generates C/C++ header files that include function signatures for declared native methods in Java. The simplest usage provides just the Java class name as an argument:
javah InteropCpp
This assumes that you want the header file to be in the same directory as the Java class file. The javah tool may require other options, depending on your application and its structure. For more information on using javah and JNI in general, see "JNI Technology" on the Sun Developer Network at http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jni.html.
Following is the generated function signature in the InteropCpp.h header file. For this tutorial, this file is generated into the directory tutorials\sdo\java-interop when you build the Java code. See Section 6.4.5.
JNIEXPORT void JNICALL Java_InteropCpp_calculateTotal (JNIEnv *, jclass, jint);
Note that, based on the Java native method's return type, the Java compiler has generated a void function in C++. Three arguments are passed to the native code from the VM:
JNIEnv |
Provides access to JNI features |
jclass |
Represents the InteropCpp class. If the native method were not static, this argument would instead be a jobject to represent this. |
jint |
the native equivalent to a Java int primitive. This represents the int handle argument in the native method. |
The macros JNIEXPORT and JNICALL define how functions are exported for shared library usage.
The class that implements the function declared in InteropCpp.h is InteropCpp.cpp, located directly in your java-interop directory. Here's the function:
JNIEXPORT void JNICALL Java_InteropCpp_calculateTotal //1
(JNIEnv *env, jclass cls, jint handle)
{
try {
printf( "[C++] accessing SDO XMLDocument\n" ); //2
JavaDataAccessService das(env); //3
DataGraphPtr graph = das.getGraph(handle); //4
DataObjectPtr root = graph->getRootObject(); //5
DataObjectPtr sum = root->getDataObject( "root.0/sum.0" );
int a = sum->getInteger( "a" ); //6
int b = sum->getInteger( "b" );
printf( "[C++] values of a and b are %i and %i\n", a, b );
printf( "[C++] setting 'aplusb' to %i\n", a+b );
sum->setInteger( "aplusb", a+b ); //7
printf( "[C++] returning control to Java\n" ); //8
fflush(stdout);
} catch (const SDORuntimeException& e) {
printf("[C++] Java_com_roguewave_rwsf_sdo_testsupport_TestSupport_cppTest\n" );
printf("[C++] caught exception: %s\n", e.why());
printf("[C++] caught exception: %s\n", e.getFileName());
printf("[C++] caught exception: %d\n", e.getLineNumber());
}
}
| //1 | The calculateTotal() method is wrapped in a try/catch block and takes three arguments: |
*env |
A pointer to the JNIEnv, the JDK environment for JNI programming |
cls |
An instance of the InteropCpp Java class |
handle |
The DataGraph handle passed in by the native method |
| //2 | Log activity to the console. |
| //3 | Create a JavaDataAccessService based on the current env. |
| //4 | Use the DAS to get the DataGraph handle. |
| //5 | Retrieve the root DataObject, from which we can then get the sum. |
| //6 | From sum, get the integers a and b, and print their values to the console. |
| //7 | Set a new integer aplusb to the sum of a + b. |
| //8 | Prepare to return control to Java, and catch any exceptions. |
At this point, the calculation is complete, and the integer aplusb is ready to be returned to the original calling Java class, Interop.java.
For convenience, the method in Interop.java that receives the response is copied below. (See Section 6.4.3 for details):
...
final int total = sum.getInt("aplusb"); //1
log("Value in 'aplusb' is " + total ); //2
...
| //1 | Return the aplusb integer inside the DataObject sum, as an integer total. |
| //2 | Log its value. |
Before building and running the example, make sure your environment is set up and that you have run the script rwsfvars. See Section 2, "Installing and Configuring," for more information.
Building this tutorial requires building both Java and C++ code, so we will use both Apache Ant, and make/nmake to compile the code.
From the directory <installdir>tutorials\sdo\java-interop, call the Ant target build to build the Java code:
<installdir>tutorials\sdo\java-interop> ant build
Note: This build target also generates the C++ header file, InteropCpp.h, using the JDK's javah tool. See Section 6.4.4.1 for more information.
To build the C++ code, change to the directory 12d, and enter:
Win32 |
nmake |
UNIX/Linux |
make |
Return to the directory java-interop, and enter:
<installdir>tutorials\sdo\java-interop> ant run-tutorials
jni.config:
run-tutorials:
[echo] Interop
[java] [Java] Creating DataObject 'sum'
[java] [Java] Setting values of 'a' and 'b' to 84 and 48
[java] [Java] Calling C++ code
[java] [C++] accessing SDO XMLDocument
[java] [C++] values of a and b are 84 and 48
[java] [C++] setting 'aplusb' to 132
[java] [C++] returning control to Java
[java] [Java] Value in 'aplusb' is 132
©2006 Copyright Quovadx, Inc. All Rights Reserved.
Rogue Wave is a registered trademark of Quovadx, Inc. in the United States and other countries. All other trademarks are the property of their respective owners.
Contact Rogue Wave about documentation or support issues.