GO: Call me maybe, Java!

When you need to pass boundaries of managed java environment, you find yourself playing (or fighting :) with C/C++ code (headers, compiler flags, linker flags ...). With version 1.5, Go is coming to save us, C/C++ is not your only option anymore...

One of the great features introduced in Go 1.5 is, being able to build a C compatible library. Using -buildmode={c-archive,c-shared} flag, you can now build a C archive go build -buildmode=c-archive or a C shared library go build -buildmode=c-shared. (For more info: Go Execution Modes)

Assume we want to create a native math library... And we need a method to multiply two long numbers.

public static long multiply(long x, long y)

JNI with C/C++

To be able to implement a method in C, first we need to define a native java method.

package io.dogan.whiteboard.jni;

public class JniMath {
    public static native long multiply(long x, long y);
}

Then using javah tool, we'll generate the C header file:

javah io.dogan.whiteboard.jni.JniMath

It will generate the header io_dogan_whiteboard_jni_JniMath.h:

#include <jni.h>
#ifndef _Included_io_dogan_whiteboard_jni_JniMath
#define _Included_io_dogan_whiteboard_jni_JniMath
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jlong JNICALL Java_io_dogan_whiteboard_jni_JniMath_multiply
  (JNIEnv *, jclass, jlong, jlong);

#ifdef __cplusplus
}
#endif
#endif

This is our contract between java JNI and native code. Implementation of io_dogan_whiteboard_jni_JniMath.c will be look like this:

#include "io_dogan_whiteboard_jni_JniMath.h"

JNIEXPORT jlong JNICALL Java_io_dogan_whiteboard_jni_JniMath_multiply
  (JNIEnv * env, jclass clazz, jlong argX, jlong argY) {

  return argX * argY;
}

And now, we can call our native multiplication method after building the shared library:

gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin io_dogan_whiteboard_jni_NativeActions.c -o libmath.dylib
System.out.println(JniMath.multiply(12345, 67890));
// output: 838102050

This is old school JNI, let's get to the topic...

JNI with Go

We'll use the same JniMath class and the same contract between java and native code. So, our Go function will use the same signature as C function and we'll export our Go function as a C function.

Assuming you already know how to export a C function in Go. If you don't then just take a look at CGO. Basic idea is, it's one by adding a special export comment over function: //export Java_io_dogan_whiteboard_jni_JniMath_multiply

Here is our Go implementation math.go:

//math.go

package main

// #cgo CFLAGS: -I/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/include
// #cgo CFLAGS: -I/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/include/darwin
// #include <jni.h>
import "C"

//export Java_io_dogan_whiteboard_jni_JniMath_multiply
func Java_io_dogan_whiteboard_jni_JniMath_multiply(env *C.JNIEnv, clazz C.jclass, x C.jlong, y C.jlong) C.jlong {
    return x * y
}

// main function is required, don't know why!
func main() {} // a dummy function

Note that C import statement and #include and #cgo instructions over the import. Alternatively, you can set CFLAGS parameter as an environment variable.

Now, let's build and run:

go build -buildmode=c-shared -o libmath.dylib math.go
System.out.println(JniMath.multiply(12345, 67890));
// output: 838102050

Done? No! Why do we need to import jni.h, use JNIEnv, jlong etc. in our parameter list and call our simple multiply method with a such strange and complex name: Java_io_dogan_whiteboard_jni_JniMath_multiply? Can it be simpler and easier?

Yes! We have a better option: JNR (jnr-ffi).

JNR with Go

Just forget about io_dogan_whiteboard_jni_JniMath.h header, Java_io_dogan_whiteboard_jni_JniMath_multiply function, #cgo, #include <jni.h> directives...

Let's implement our multiplication as it's supposed to be math.go:

// math.go

package main

//export Multiply
func Multiply(x int64, y int64) int64 {
    return x * y
}

// main function is required, don't know why!
func main() {} // a dummy function

We also need an java interface matching the signature of Go function MathLib:

package io.dogan.whiteboard.jnr;

public interface MathLib {
    long Multiply(long x, long y);
}

We have a Go function, we have a java interface... Let's bind them to each other...

Build go library:

go build -buildmode=c-shared -o libmath.dylib math.go

Load the library using JNR-FFI and call the method:

package io.dogan.whiteboard.jnr;

import jnr.ffi.LibraryLoader;

public class JnrMath {

    private static final MathLib MATH_LIB;

    static {
        MATH_LIB = LibraryLoader.create(MathLib.class).load("math");
    }

    public static void main(String[] args) {
        System.out.println(MATH_LIB.Multiply(12345, 67890));
        // output: 838102050
    }
}

PS1: Go 1.5 is not released yet by the time this post is written. To be able to test things here, you need to build/install Go 1.5 from source. For more info: Installing Go from source.

PS2: libmath.dylib should be placed in a library directory which is known by JVM; either in a system-wide lib directory or in a path denoted by LD_LIBRARY_PATH.

Caution: Because both Java and Go are garbage collected languages, watch out for possible reference tracking issues between two. For more info: Binding Go and Java

Tweet
comments powered by Disqus