CXXWRAP Design

Table Of Contents
Introduction: what is cxxwrap?
JNI code generation
General line of attack
Argument conversion
Finalization and destuction
Extending classes in java
Adding on to an already wrapped API

Introduction: what is cxxwrap?

cxxwrap is a software engineering tool which scans C++ header files defining a set  of classes.  Once the class interface definitions have been scanned, cxxwrap can

JNI code generation

The primary reason cxxwrap was written is to generate JNI code to bind a Java API to one or more C++ libraries.

General line of attack

All java objects are "pointers", in the C++ sense.  (For example, the assignment operator in Java is defined to be a pointer copy.)  The diagram below shows how cxxwrap keeps a Java object tied to a C++ implementation.  Invocations of methods on the object are decoded by JNI C++ code, and result in invocations on the C++ object implementation.
MyObject jA = new MyObject(); // invokes C++ constructor
jA.foo(37);
JNI connection in cxxwrap
Note that if some Java code copies an object with the assignment operator,  both the original and new object pointers are tied to a single instance of the C++ implementation:
MyObject jB = jA; // does not invoke C++ constructor
Pointers in cxxwrap
If the wrapped API provides another way to access a refererence to the C++ object, one can also end up with two separate Java objects, each of which refers to the same C++ object.  This is a fundamendally different case than the last example:
MyObject jA = someObject.getContainedA();
MyObject jB = anotherObject.getContainedA();
Duplicate pointers in cxxwrap
In the diagram above, both jA and jB refer to the same object, but Java doesn't know this.  It should now be obvious that dealing with garbage collection is going more difficult in a wrapped Java API (more about that later.)

Argument conversion

The story gets a bit trickier in the case of non-primitive method arguments.  In this case, the JNI code layer must do more work, using Sun's JNI interface APIs to gain access to a suitable representation of the JVM's internal data:
jA.foo(new int[3] {1, 2, 3});
Argument conversion in cxxwrap
(The JNI code shown above is schematic.  Refer to the Sun API to see the real names of the JNI interface methods used to convert data.)

One important point to make is that during the course of the method invocation, a temporary copy of the entire array may be made, which is then destroyed prior to the method return.  So, data pointers cannot be transported across the Java/C++ boundary.  This is a severe and fundamental limitation with the JNI interface (*).  Applications must be sure not to rely on the contents of array arguments after invocation of one of the cxxwrap JNI methods.

cxxwrap can use this method to convert all arrays of primitives, as well as the java.lang.String primitive type, which becomes a C++ "char*".  The complete list of type mappings from C++ to Java implemented by cxxwrap is as follows:
 

C++ type Java type
void void
int int
short int short
long int long
float float
double double
bool boolean
char
unsigned char
byte
[const] char* java.lang.String 
(converted using UTF)
[const] unsigned char* byte[]
[const] int* int[]
[const] short int* short[]
[const] long int* long[]
[const] float* float[]
[const] double* double[]
[const] bool* boolean[]
C C
[const] C* C
[const] C& C

Finalization and destruction

When a Java object is deleted (by the JVM, which will also invoke the object's finalize() method), the C++ implementation underneath could also be deleted... right?  No, because there may be other C++ objects in the system, hidden from the JVM, which are holding and using pointers to the C++ implementation.  We saw before that this can happen even on the Java side, if multiple references to the same underlying C++ object are obtained using the wrapped API.  (There are other approaches to wrapping interfaces in Java which can co-exisit peacefully with garbage collection, but they incur a heavy performance price, such as maintainence of a large object mapping table.)

cxxwrap provides an explicit method on all Java objects called delete(), which invokes the C++ destructor.  Applications should use delete() just as they would in C++, following the analogy that all Java objects are pointers.  If a wrapped Java object fails to call delete(), a memory leak can occur on the C++ heap.

jA.delete(); // invokes C++ destructor
Destructors in cxxwrap

Extending classes in java

One consequence of the approach cxxwrap uses to implement java wrappers is that the resilting java classes cannot be extended, in general, by deriving new subclasses in java.  That's because when the C++ implementation invokes a method from the object's virtual table, it's always the C++ method, never the new java method, which gets invoked.  For example, lets say you have the following cxxwrapped class interface:
public class A {
    public A();
    public void foo();
}
and you then derive a new class in java:
public class B extends A {
    public void foo() { System.out.println("B foo!"); }
}
You can then create an object of class B and pass it, as an A, to some C++ wrapped API.  But when C++ invokes the foo() method on your object, only A's foo() gets called -- never B's foo() -- because the C++ virtual table cannot contain a pointer to the java version of foo() in class B.

cxxwrap allows you to extend abstract classes in java, for any pure virtual method.  If you define A as follows in C++:

class A {
public:
    A();
    A(int bar);
    virtual void foo() = 0;
};
then cxxwrap will generate constructors for class A in java, which yield java objects whose pure virtual methods can be extended.  Now, when you write class B as shown above, the java version of foo() will be invoked whenever C++ invokes foo().  This is accomplished by generating a hidden adapter class in C++ for every abstract class.  It is the hidden adapter whch gets constructed by class A's constructors, not a C++ class A directly.

If your native code creates native threads (e.g pthreads) which are compatible with the JVM you run in, these threads can invoke Java virtual methods via a cxxwrap API.  When a Java method is invoked in cxxwrap-generated C++ code, the JNI thread attach API is used to ensure that the calling thread is known to the JVM.

Adding on to an already wrapped API

Let's say you are given a cxxwrap JNI interface to a set of C++ classes, for which you also have the C++ header files (let's say "vendor/*.h".  You'd like to add some more classes from your own package, whose C++ header files are in "local/*.h".  Here's how to invoke cxxwrap to do so:
cxxwrap --jni --jni-packages=local vendor/*.h local/*.h
The "--jni-packages" argument tells cxxwrap to emit JNI interfaces only for your files.  cxxwrap still needs to scan the vendor files, to build up its internal database of objects in the vendor package(s).

A more compact and reliable way to accomplish this is to use the "--write-cache" and "--read-cache" options of cxxwrap.  The following example is taken from the regression test for cxxwrap:

First, the base library is wrapped:

cxxwrap --verbose --package-prefix=org.part1 --jni \
    --write-cache=examples/part1.cache examples/part1.h
Here, the classes found in "examples/part1.h" will be assigned to the package "org.part1.examples", and all classes and template definotions will be saved in "examples/part1.cache" for later use.  Next, the add-on library which depends on classes in "examples/part1.h" is wrapped:
./cxxwrap --verbose --package-prefix=org.part2 --jni \
    --read-cache=examples/part1.cache examples/part2.h
Here, the new classes defined in "examples/part2.h" are assigned to the "org.part2.examples" package, but they can still refer to classes from the first wrapping session. No "--jni-packages" option is needed, because cxxwrap marks classes read from a cache as already wrapped and will not wrap them again.

This pattern can be extended to an arbitrary number of dependent libraries, by using more than one "--read-cache: option on the later wrapping sessions.



*This limitation may be intentional on Sun's part as a part of a deliberate business strategy.  If only JVM developers can implement high-performance native code under a Java API, then they gain a significant strategic advantage in implementing advanced media APIs which require data transport from Java to C++.