Permalink
Fetching contributors…
Cannot retrieve contributors at this time
303 lines (234 sloc) 12.1 KB

Creating Java Bindings Guide

Overview

The "Creating Java Bindings Guide" serves as a basic tutorial for using the SWIG software development tool to create 'glue code' required for Java to call into C/C++ code. It contains: guides for dealing with type conversions, exception handling, callbacks; recommendations on how to write/modify the native API to avoid issues on the Java side, and also workarounds for those issues that can't be avoided.

This guide was created with the upm and mraa libraries in mind, and uses examples taken from these sources, but its usage can be extended to any project of creating Java bindings for C/C++ libraries.

Tools of trade

SWIG General Documentation

SWIG Java-specific Documentation

Recommendations for the native API

Pointers

As much as possible, avoid passing values/returning values through pointers given as as arguments to methods. As the Java language does not have pointers, SWIG provides a workaround in the typemaps.i library.

Alternatives:

  1. Functions that read data from a driver, return it through a pointer given as argument, and return a bool value, should be replaced by functions that return the value directly and throw a std::runtime_error if a read error occurs. E.g.:
/*
 * Function reads from sensor, places read value in variable bar and
 * returns true if succesful. Function returns false if read failed.
 */
bool func(int *bar);

Replaced by:

/*
 * Function reads from sensor and returns read value.
 * Or throws std::runtime_error if a read error occurs
 */
int func();
  1. Functions that return multiple values through pointers, that make sense to be grouped together into an array1 (e.g. speed values, acceleration values), should be replaced by functions that return a pointer to an array in which the elements are the returned values. Afterwards, wrap the C array with a Java array. E.g.:
/*
* Function returns the acceleration on the three
* axis in the given variables.
*/
void getAccel(int *accelX, int *accelY, int *accelZ);

Replaced by:

/*
 * Function returns the acceleration on the three
 * axis as elements of a 3-element array.
 */
int *getAccel();
  1. Functions that return N values through pointers, that do not make sense to grouped together (e.g. a general purpose function that returns both the light intensity and air pollution), should be replaced by N functions (one for each value) that read only one specific value. E.g.:
/*
 * Function returns the light intensity and air pollution
 */
void getData(int *light, int *air);

Replaced by:

int getLight();
int getAir();
  1. Functions that return N values through pointers; values that do not make sense to be grouped together, but are time dependent, and make sense to be read at the same time. For example, a sensor that reads air humidity and temperature. A user may want to know the temperature value temp read at the exact moment the humidity value humid was read. These should be replaced by N+1 functions: a getData() function that reads all values at the same time and stores them in global variables; and N getter functions, one for each value. E.g.
/*
 * Function returns the light intensity and air pollution
 */
void getData(int *temp, int *humid);

Replaced by:

void getData();
int getTemp();
int getHumid();

1this depends on the interpretation of the returned data. For example, arguments that return the temperature and light intensity, don't make sense to be grouped into an array of size 2. But acceleration on the three axis can be grouped together in an array of size 3. where accelX is accel[0], accelY is accel[1], accelZ is accel[2].

Notice: Sometimes, you may be required to write JNI code. Be aware of the difference between the C JNI calling syntax and the C++ JNI calling syntax.The C++ calling syntax will not compile as C and also vice versa. It is however possible to write JNI calls which will compile under both C and C++ and is covered in the Typemaps for both C and C++ compilation section of the SWIG Documentation.

Throwing Exceptions in Java

Language independent:

The %exception directive allows you to define a general purpose exception handler. For example, you can specify the following:

%exception [method_name] {
    try {
        $action
    }
    catch (std::invalid_argument& e) {
        ... handle error ...
    }
}

If [method_name] is not specified then the directive is applied to all methods in its scope.

The usual thing you'd want to do is catch the C++ exception and throw an equivalent exception in your language.

The exception.i library file provides support for creating language independent exceptions in your interfaces. To use it, simply put an "%include exception.i" in your interface file. This provides a function SWIG_exception() that can be used to raise common language exceptions in a portable manner. For example :

// Language independent exception handler
%include exception.i

%exception {
    try {
            $action
        } catch(OutOfMemory) {
            SWIG_exception(SWIG_MemoryError, "Out of memory");
    } catch(...) {
            SWIG_exception(SWIG_RuntimeError,"Unknown exception");
    }
}

In the upm library, the upm_exception.i interface file provides the functionality to catch common exceptions and propagate them through SWIG. It uses the exception.i library file and is language independent.

The upm_exception.i interface file is included in the upm.i file, therefor SWIG wraps all generated methods' body in a try-catch statement for the following exceptions:

  • std::invalid_argument
  • std::domain_error
  • std::overflow_error
  • std::out_of_range
  • std::length_error
  • std::logic_error
  • std::bad_alloc
  • std::runtime_error
  • std::exception

Java specific:

To throw a specific Java exception:

%exception {
    try {
        $action
    } catch (std::out_of_range &e) {
        jclass clazz = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(clazz, "Range error");
        return $null;
    }
}

Where FindClass and ThrowNew are JNI functions.

Java defines two tipes of exceptions: checked exception and unchecked exceptions (errors and runtime exceptions). Checked exceptions are subject to the Catch or Specify Requirement.

The C++ compiler does not force the code to catch any exception.

The %exception directive does not specify if a method throws a checked exception (does not add classes to the throws clause). For this, the %javaexception(classes) directive is used; where classes is a string containing one or more comma separated Java classes.

%javaexception("java.lang.Exception") {
    try {
        $action
    } catch (std::out_of_range &e) {
        jclass clazz = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(clazz, "Range error");
        return $null;
    }
}

In the upm library, the java_exceptions.i library file provides the functionality to catch exceptions and propagate them through SWIG as Java checked exceptions. The file provides SWIG wrappers, in the form of macros, that can be applied to methods.E.g. use the READDATA_EXCEPTION(function) macro for functions that read data from a sensor and throw a std::runtime_error in case of a read failure. This will result in:

void function throws IOException ();

Caveats & Challenges

Wrapping C arrays with Java arrays

SWIG can wrap arrays in a more natural Java manner than the default by using the arrays_java.i library file. Just include this file into your SWIG interface file.

Wrapping unbound C arrays with Java arrays if array is output

Functions that return arrays, return a pointer to that array. E.g.:

/*
 * Function returns the acceleration on the three
 * axis as elements of a 3-element array.
 */

int *getAccel();

SWIG:

%typemap(jni) int* "jintArray"
%typemap(jstype) int* "int[]"
%typemap(jtype) int* "int[]"

%typemap(javaout) int* {
    return $jnicall;
}

%typemap(out) int *getAccel {
    $result = JCALL1(NewIntArray, jenv, 3);
    JCALL4(SetIntArrayRegion, jenv, $result, 0, 3, (const signed int*)$1);
}

Wrapping unbound C arrays with Java arrays if array is input

In C, arrays are tipically passed as pointers, with an integer value representig the length of the array. In Java, the length of an array is always known, so the length argument is redundant. This example shows how to wrap the C array and also get rid the length argument. E.g.:

void func(uint8_t *buffer, int length);

SWIG:

%typemap(jtype) (uint8_t *buffer, int length) "byte[]"
%typemap(jstype) (uint8_t *buffer, int length) "byte[]"
%typemap(jni) (uint8_t *buffer, int length) "jbyteArray"
%typemap(javain) (uint8_t *buffer, int length) "$javainput"

%typemap(in,numinputs=1) (uint8_t *buffer, int length) {
	$1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);
	$2 = JCALL1(GetArrayLength, jenv, $input);
}

!!!! There is a difference between TYPE *name and TYPE * name in typemaps!!!!!

Implementing callbacks in Java

Callbacks in the UPM Java library (as well as the MRAA Java library) make use of the void mraa_java_isr_callback(void* data) method from MRAA.

Callbacks in the UPM Java library are implemented as follows (we use the a110x Hall Effect sensors as example):

We extend the sensor class with another method, installISR(jobject runnable), which is a wrapper over the original installISR(void (*isr)(void *), void *arg) method. This will install the mraa_java_isr_callback() method as the interrupt service routine (ISR) to be called, with jobject runnable as argument.

Java callbacks are added at the SWIG interface level. For ease-of-use, a collection of macros are available in src/_upm.i.

src/a110x/a110x.i:

JAVA_ADD_INSTALLISR(upm::A110X)

Will expand to the following SWIG wrapper code:

SWIGINTERN void upm_A110X_installISR__SWIG_1(upm::A110X *self,jobject runnable){
self->installISR(mraa_java_isr_callback, runnable);
}

To use callback in java, we create a ISR class, which implements the Java Runnable interface, and we override the run() method with the code to be executed when the interrupt is received. An example for the a110x Hall sensor that increments a counter each time an interrupt is received:

public class A110X_intrSample {
    public static int counter=0;

    public static void main(String[] args) throws InterruptedException {
        upm_a110x.A110X hall = new upm_a110x.A110X(2);

        A110XISR callback = new A110XISR();
        hall.installISR(callback);

        while(true){
            System.out.println("Counter: " + counter);
            Thread.sleep(1000);
        }
    }
}

class A110XISR implements Runnable {
    public A110XISR(){
        super();
    }
    public void run(){
        A110X_intrSample.counter++;
    }
}