Hello jni

References:

./code/jni/hello/Makefile
CXXFLAGS := -I $(JAVA_HOME)/include
CXXFLAGS += -I $(JAVA_HOME)/include/linux

all: NativeSample.jar libhello.so

NativeSample.jar: NativeSample.kt Main.kt
	kotlinc-jvm -include-runtime -d NativeSample.jar NativeSample.kt Main.kt

libhello.so: hello.cc hello.h
	$(CXX) -o $@ -shared -fPIC $(CXXFLAGS) hello.cc

run: all
	java -jar NativeSample.jar

clean:
	$(RM) *.jar *.so

Note: We have to disable name mangling here.

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html describe the specification:

  • Java_NativeSample_sayHello: The prefix is fixed with Java. NativeSample is the class name in kotlin. sayHello is the method in kotlin.

./code/jni/hello/hello.h
#ifndef HELLO_H
#define HELLO_H
#include "jni.h"

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL Java_NativeSample_sayHello(JNIEnv *env, jobject obj);

JNIEXPORT jfloat JNICALL Java_NativeSample_sum(JNIEnv *env, jobject obj,
                                               jfloatArray array);

#ifdef __cplusplus
}
#endif

#endif // HELLO_H

The first argument must be JNIEnv *env. Since it is a non-static kotlin method, the second argument is a reference to the object. Otherwise, it is a reference to the class.

./code/jni/hello/hello.cc
#include "hello.h"

JNIEXPORT void JNICALL Java_NativeSample_sayHello(JNIEnv *env, jobject obj) {
  std::cout << "hello world\n";
}

JNIEXPORT jfloat JNICALL Java_NativeSample_sum(JNIEnv *env, jobject obj,
                                               jfloatArray array) {
  // the last arg is set to nullptr, which means we are not interested
  // in whether the returned pointer points to a copied region or not.
  jfloat *p = env->GetFloatArrayElements(array, nullptr);
  jsize n = env->GetArrayLength(array);

  jfloat s = 0;
  for (int32_t i = 0; i != n; ++i) {
    s += p[i];
  }
  // See
  // https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
  // JNI_ABORT: means to free the buffer pointed by p but don't copy the
  // buffer to array
  env->ReleaseFloatArrayElements(array, p, JNI_ABORT);

  return s;
}

System.loadLibrary("hello"):

  • On Linux, it looks for libhello.so

  • On Windows, it looks for hello.dll

external fun sayHello():

  • It will look for the function with name Java_NativeSample_sayHello.

./code/jni/hello/NativeSample.kt
class NativeSample {
  companion object {
    init {
        System.loadLibrary("hello")
    }
  }

  external fun sayHello()
  external fun sum(array: FloatArray): Float
}
./code/jni/hello/Main.kt
fun main() {
  var s = NativeSample()
  s.sayHello()
  var a : FloatArray = listOf(1.2f, 3.0f).toFloatArray()
  println(s.sum(a)) // 4.2

  a = floatArrayOf(3.0f, 4.0f, 5.0f)
  println(s.sum(a)) // 12.0

  a = FloatArray(100) {it.toFloat()}
  println(s.sum(a)) // 4950.0
}