INTRODUCTION
 

The name "Jirk++" stands for "Java Hierarchy in C++" (it is pronounced "Jeerk" like in "Jeep"). This mechanism provides an easy manipulation of Java classes directly in C++. It is based on the Java Native Interface (JNI) provided with any Java development kit (e.g. the Java Development Kit of Sun Microsystems).

JNI provides low-level functions in C to interact with the Java Virtual Machine (JVM) and all the components it manages, more specifically any Java class or object you want. It is a complete and efficient interface but it is difficult to manipulate it, the access to a single class can take several lines of C code and requires some knowledge of JNI's mechanism. Jirk++ proposes an encapsulation of these low-level functions and the mapping of any Java class you want so you can manipulate them as if they were C++ classes.

The goals of Jirk++ are to provide an easiest way to use C++ code in Java and to provide a fully portable graphic interface for C++ through the possibility of using directly Java components. Jirk++ is, for the moment, part of the of which the binary code and the source code are free of charge to use, access or modify under .

This document first presents briefly JNI and its facilities. Then we introduce Jirk++, its mechanism and how it can be useful. The rest of the section explains how to use Jirk++ through some examples.

 
WHAT IS JNI ?
 

JNI is an interface between Java and C/C++ code. It is possible to call a C/C++ function from Java code. You just have to declare a method in your Java class that is specified as "native", meaning the core of the function is native code, i.e. machine dependent code. In C/C++, you have to define a function that will be recognized as the native method of your class, the identification of the function is performed using its name. For instance, the method named compute in the Java class Algorithm will be associated with the function named Java_Algorithm_compute in C/C++. In order that JNI finds the function, you have to specify in the Java class which library contains the function. This library has to be a dynamic-link library (e.g. *.dll, *.so...).

Of course, these C/C++ functions can receive arguments. Some of them are needed for technical issues (e.g. which JVM is used) and the others are classical arguments as any Java method can receive. So the C/C++ functions representing Java native methods can receive primitive types from Java (like integers, floating points, booleans...) and references to Java objects. JNI provides facilities to manipulate these references from the C/C++ side.

This mechanism is interesting if your application is Java oriented and if you only need limited interaction with C++. But if your application is mostly C++ oriented, you may prefer to load a JVM into your C/C++ program and use Java from there. Fortunately JNI extends its facilities to manipulate Java classes within any C/C++ function, not only those representing a Java native method.

To resume, from the Java side you can only call C/C++ functions with eventually Java arguments, but you can not manipulate structures or classes of the C/C++ side. From the C/C++ side, you have more facilities and you can manipulate anything you want from the Java side. If you want to know more about JNI, here is an excellent book (the one we use to develop Jirk++): The Java Native Interface: Programmer's Guide and Specification, Sheng Liang, 1999, Addison-Wesley. You can find parts of this book in The Java Tutorial.

 
WHAT IS JIRK++ ?
 

Jirk++ only works on the C/C++ side, so nothing from the Java side has changed compared to JNI. Jirk++ proposes first an encapsulation of the C functions of JNI into C++ classes to mask the complex JNI mechanism and provides a fully object-oriented interface. Hence, you do not have to know details on JNI to load, for instance, a JVM into your C++ program. Jirk++ also provides, and it is its most important contribution, a C++ representation of any Java class you want so you can manipulate it as a C++ class. At your request, Jirk++ can create a C++ class that possesses the same methods, attributes and inheritance as the Java class it represents. This encapsulation has naturally a cost, Jirk++ will obviously be slower than a direct and efficiently-designed call to JNI. In a specific situation, a good programmer will do a better job that the generic approach of Jirk++. When designing Jirk++, we favored easiness of use to efficiency of execution.

Jirk++ is useful if you want for instance a portable graphic interface for a C++ program. For that, you have to load a JVM, then you can call Java classes as any C++ class in your program. Nowadays we can suppose that any environment has a JVM or can easily get one. Jirk++ is also useful if you want to use C++ functions from the Java side (for performance or reuse reasons), because Jirk++ can manipulate the Java arguments received as C++ elements. The following of this section explains how to get Jirk++ and presents some examples on how to use it.

 
HOW TO GET IT ?
 

As we said before, Jirk++ is part of the B++ Library which is a C++/Java library. To get Jirk++, you will have to download the B++ Library and compile it (at least the modules needed for Jirk++). Unfortunately, due to our goal of portability and the fact that it is a hybrid library (both Java and C++ code), the compilation is a bit tricky. The B++ Library and documentation are available . Jirk++ is contained in the bpp_java library so your programs will have to be linked against it. As you will see later, the C++ representation of the Java classes are organized in libraries named jirk*. You will also need them when linking your program. How to compile all these libraries is explained in the section on and we will not write a word about it in this section.

 
NOTATIONS
 
 
Classes
 

With each Java class is associated a C++ class that maps its attributes and methods, e.g. the class named jirk::java::lang::jaString in C++ represents the java.lang.String class from Java. The C++ class is in a namespace that identifies the package of the corresponding Java class (e.g. jirk::java::lang = java.lang) and its name begins with the letters ja (e.g. jaString). For an inner class, the representation is a bit different, an underscore is used to separate the class from its parent, e.g. the java.awt.PageAttributes.ColorType inner class from Java will be associated with jirk::java::awt::jaPageAttributes_ColorType in C++.

When you use a Java class, the header file containing the interface of its C++ representation must be included in the C++ code. For instance, for the java.lang.String class, the file named jirk/java/lang/string.hpp as to be included. The file name is the same as the class, except there is no upper case. For an inner class, the file to include is the file of the class that contains it.

The inheritance hierarchy of Java is reproduced in C++, e.g. as the java.lang.String class inherits from java.lang.Object, the jirk::java::lang::jaString class inherits from jirk::java::lang::jaObject. The only difference with Java is that no distinction is made between an interface and a class.

 
Primitive Types
 

The primitive types of Java are represented in C++ with a name beginning with the letters jy. Here is the name of each Java primitive type in C++.

Java C++
boolean jyBoolean
byte jyByte
char jyCharacter
short jyShort
 
Java C++
int jyInteger
long jyLong
float jyFloat
double jyDouble

When a function receives arguments from Java, it will always be either primitive types or JNI references to objects that will have the jyObject type.

 
Properties Access
 

All the properties of a Java class begin with the letters j_ in C++ to avoid any collision with C++ keywords. The usual distinction between instance and class properties is still preserved. Here are some examples.

  • jaMyObject.j_display(...)
    Calls the display method of MyObject instance.

  • jaMyClass::j_init(...)
    Calls the init method of MyClass class.

  • jaMyClass::j_new(...)
    Calls a constructor of MyClass.

  • jaMyObject.j_message()
    Returns the value of the message attribute of MyObject.

  • jaMyObject.j_message("Oops !")
    Modifies the value of the message attribute of MyObject.

 
Strings
 

Java strings are represented by the jirk::java::lang::jaString class in C++. However, additional methods are provided in the C++ class so a C++ string (i.e. char *) can easily be assimilated with a Java string and vice versa. To create a Java string from a C++ string, there is the constructor jaString::j_new(const char *). To create a C++ string from a Java string, there is the method native.

 
Arrays
 

Jirk++ provides a generic class to represent unidimensional or multidimensional Java arrays. The class is bpp::java::clArray<T,D> where T is the base type of the array and D its number of dimensions, e.g. a unidimensional array of strings will be declared clArray<jaString,1> and a two-dimensions array of integers will be declared clArray<jyInteger,2>. To build such arrays, here are some constructors.

  • clArray<jyInteger,2> a();
    Builds a null reference to an array of integers with 2 dimensions.

  • clArray<jyInteger,2> a(nil,4,3);
    Builds an array of integers with 4 elements in the first dimension and 3 in the second.

  • clArray<jyInteger,2> a(t,4,3);
    Builds an array of integers with 4 elements in the first dimension and 3 in the second. The content of the array is created by copying the content of the C++ array t. This array must be dynamic, i.e. with the jyInteger ** signature. Static arrays, except those with one dimension, can not be used to initialize a Java array.

To access an element of an array, you must use the operator () or the operator []. The operator () is used to access a subarray of a multidimensional array, and the operator [] is used to access an element of a unidimensional array, e.g. to access the second element of a unidimensional array a, you must use a[2], but to access the element at the second position in the first dimension and at the third position in the second dimension of a matrix m, you must use m(2)[3].

 
SOME EXAMPLES
 

For these examples, we suppose that the JVM is loaded manually into the program, which means it is C++ that calls Java. We will see later an example where it is Java that calls C++.

 
Example 1
 

The following example defines a function that creates a vector with 10 strings containing "message".

#include <jirk/java/lang/string.hpp>
#include <jirk/java/util/vector.hpp>

using namespace jirk::java::lang;
using namespace jirk::java::util;

void example1(void) {
 bpp::displayConsole::clDisplay display;

 bpp::standard::openLibrary(display);
 bpp::java::loadVirtualMachine();

 {
  jyInteger i = 10;
  jaVector  v = jaVector::j_new();

  while (i>0) {
   v.j_add(jaString::j_new("message"));
   --i;
  }
 }

 bpp::java::unloadVirtualMachine();
 bpp::standard::closeLibrary();
}

Note that the variable v is a reference to an instance of the Vector class, just like in Java. The semantic of Java for the variables is the same in C++ for the variables representing Java elements. For more comprehension, here is the equivalent Java code of the function's body.

int    i = 10;
Vector v = new Vector();

while (i>0) {
 v.add(new String("message"));
 --i;
}

The function loadVirtualMachine from the java module of the B++ Library loads a JVM into the program and unloadVirtualMachine unloads it. Only one JVM can be loaded in a program. Moreover, if it is Java that calls the function, you can not load a JVM, you have to get the one that calls the function. We will see later how to do it. Finally, Jirk++ is part of the B++ Library, which needs to be initialized before any use; and after any use, it has to be closed. The functions for that are openLibrary and closeLibrary of the Standard module. For more details, see the section .

In the C++ example above, the braces (i.e. { and }) after loadVirtualMachine and before unloadVirtualMachine are very important, because all the references to Java objects must be created after the loading and must be deleted before the unloading of the JVM. Without the braces, the deletion of the variable v, for instance, would appear at the closing of the function, so just after the unloading of the JVM.

 
Example 2
 

The following example defines a function that displays on the standard output the strings contained in a vector.

void example2(jaVector v) {
 jyInteger i = 0;

 while (i<v.j_size()) {
  cout << (jaString)(v.j_elementAt(i)) << endl;
  ++i;
 }
}

Here is the equivalent Java code of the function.

public void example2(Vector v) {
 int i = 0;

 while (i<v.size()) {
  System.out.println((String)(v.elementAt(i)));
  ++i;
 }
}

In this example, there is no loading of the JVM, we suppose it is done before. In fact, you have to load the virtual machine at the beginning of your program and unload it at the end. Avoid repetitive loads and unloads of the JVM, it will be extremely time consuming. Moreover, when a JVM is unloaded, everything that was in its memory is lost.

 
Example 3
 

This example shows how to manipulate Java arrays.

bpp::java::clArray<jyInteger,2> a(nil,10,5);
jyInteger                       i = 0;
jyInteger                       j;

while (i<10) {
 j=0;

 while (j<5) {
  a(i)[j]=i*j;
  ++j;
 }

 ++i;
}

Here is the equivalent Java code.

int a[10][5];
int i = 0;
int j;

while (i<10) {
 j=0;

 while (j<5) {
  a[i][j]=i*j;
  ++j;
 }

 ++i;
}

In the C++ example, there is technically only one call to the JVM for the creation of the array. In fact, the array exists once in the Java side and once in the C++ side. When a modification of the array occurs in one side, it does not occur in the other side. The update occurs only when the data of the array needs to be transferred from one side to another. To update explicitly the array in a side, you call either the synchronizeJavaSide method or the synchronizeNativeSide method of the array.

 
NATIVE METHODS
 

We present here how to call a C++ function from the Java side. For that, you first have to declare a native method in a Java class to represent the function you want to call. Somewhere in the Java class, you also need to load the dynamic library in which the function is embedded, using the loadDynamic method of the EnvironmentLoader class. As Jirk++ is part of the B++ Library, the methods open and close need to be called respectively to initialize and terminate the B++ Library. Here is an example.

class French {
 public native String convert(String s);

 public int main(String argv[]) {
  ...
  bpp.java.EnvironmentLoader.loadDynamic("mylib");
  bpp.java.EnvironmentLoader.open(System.out,true);
  ...
  French f;
  String s = f.convert("blabla...");
  ...
  bpp.java.EnvironmentLoader.close();
  ...
 }
}

In the C++ side, the function associated with the convert method of French is named Java_French_convert. Here is an example of what the function should look like.

using namespace jirk::java::lang;
using namespace bpp::java;

jyObject Java_French_convert(jyContext c,jyObject,jyObject s) {
 getVirtualMachine(c);

 jaString s1(s);
 jaString s2;

 s2=...

 return_object(s2);
}

The first argument received is what we call the JNI context or environment. It is through this object that we communicate with a JVM. The second argument is a JNI reference to the object the native method belongs to. The other arguments represent those of the Java method. In our case, there is only a string for which we receive a JNI reference.

The first thing to do is to get the JVM that calls the function. For that, we must use the getVirtualMachine function that needs the context as argument. Then the JNI references we need must be encapsulated into C++ objects. In our example, only the s reference is encapsulated into s1. We also declare a Java reference s2 to be used in the function.

As you can see in the example, it is possible for a native method to return an object to Java. Technically, it is a JNI reference that is returned. That explains we do not use directly the return keyword but the return_object macrocommand. To return an array, you will have to use return_array (because the array has to be synchronized with the Java side before returning). This feature is available in Jirk++ only if you have a Java 2 Virtual Machine.

 
EXCEPTIONS
 

Exceptions thrown from Java can be checked or caught in C++ with a call to methods of the current JNI context, which you can get by calling the default constructor of bpp::java::clContext. Here are examples.

  • if (clContext().checkException()) ...
    Checks if an exception is pending in the current thread.

  • jaException e = clContext().getException();
    Gets the exception pending in the current thread.

From C++, Java exceptions can be thrown using the throwException method of the current JNI context. Java will catch them the usual way. Here is an example that illustrates how to throw an exception.

clContext().throwException(jaException::j_new("Ooops !"));
 
THREADS
 

Even if you have several threads in your application, there can be only one JVM. In order that a thread can use the JVM, it must be attached to it using the attachThread method of the virtual machine, which you can get by calling the bpp::java::virtualMachine function. To detach the thread from the JVM, use the detachThread method.

 
ADD A CLASS
 

By default Jirk++ proposes a C++ representation of some usual Java classes. If you want to add other classes, you have to use the following command from the root folder of the B++ Library.

java -cp library/java bpp.program.BuildJirk -path sources
 +add <your_class>

Replace <your_class> by the class or the classes you want to add. It will create the C++ representation of the class(es) and any class it depends on. Only the C++ representation of the missing classes is added. After that, the makefile and then the compilation have to be completed. For that, look at the section on .

 
CONCLUSION
 

The examples presented here try to show how Jirk++ can facilitate the manipulation of Java elements in C++. Everything is not shown and for more information you will have to look at the documentation of the Java module of the B++ Library available in the section on . As we said before, the tricky part will be for you to compile Jirk++ !