Native call Java

  • NativeCallJavaTest.kt
package com.practice

class NativeCallJavaTest {

    external fun test()

    init {
        System.loadLibrary("native-call-java-lib")
    }
}

Call the member methods

  • Counter.kt
      package com.practice

      class Counter {

      private var count = 0


      fun add() {
          println("Counter#add(): old count is $count")
          count++
      }


      fun count(): Int {
          println("Counter#count(): $count")
          return count
      }

    }
  • native-call-java-lib.cpp
      #include <jni.h>
      #include <android/log.h>
      #include <iostream>

      #define TAG "counter"

      jint getCount(JNIEnv *env, jclass cls, jobject obj) {
          jmethodID methodId = env->GetMethodID(cls, "count", "()I");
          return env->CallIntMethod(obj, methodId);
      }

      void addCount(JNIEnv *env, jclass cls, jobject obj) {
          jmethodID methodId = env->GetMethodID(cls, "add", "()V");
          env->CallVoidMethod(obj, methodId);
      }

      extern "C" JNIEXPORT void JNICALL
      Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject thiz) {
          __android_log_print(ANDROID_LOG_DEBUG, TAG, "test()");

          // new object
          jclass cls = env->FindClass("com/practice/Counter");
          jmethodID constructId = env->GetMethodID(cls, "<init>", "()V");
          jobject obj = env->NewObject(cls, constructId);

          // get count
          __android_log_print(ANDROID_LOG_DEBUG, TAG, "count is %d", getCount(env, cls, obj));

          // add count
          addCount(env, cls, obj);
          __android_log_print(ANDROID_LOG_DEBUG, TAG, "count is %d", getCount(env, cls, obj));
      }

Call the static method of companion object

  • Counter.kt
class Counter {

  companion object {
    @JvmStatic
    fun getName(): String {
      return "Kotlin static method is called."
    }
  }

}
  • native-call-java-lib.cpp
jstring getName(JNIEnv *env) {
  jclass cls = env->FindClass("com/practice/Counter");
  if (cls == nullptr) {
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "Counter/$Companion is not found");
  }
  jmethodID methodId = env->GetStaticMethodID(cls, "getName", "()Ljava/lang/String;");
  if (methodId == nullptr) {
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "Counter.getName is not found");
  }

  return (jstring) env->CallStaticObjectMethod(cls, methodId);
}

extern "C" JNIEXPORT void JNICALL
Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
  jstring jName = getName(env);

  const char *name = env->GetStringUTFChars(jName, 0);
  __android_log_print(ANDROID_LOG_DEBUG, TAG, "name is %s", name);
  env->ReleaseStringUTFChars(jName, name);
}

Call the static method from singleton

  • Singleton.kt
package com.practice

object Singleton {

    @JvmStatic
    fun getValue(input: Int): Int = input * 2

}
  • native-call-java-lib.cpp
jint getValue(JNIEnv *env, jint input) {
    jclass cls = env->FindClass("com/practice/Singleton");
    if (cls == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "Singleton class not found");
    }

    jmethodID methodId = env->GetStaticMethodID(cls, "getValue", "(I)I");
    if (methodId == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "Singleton.getValue(int) not found");
    }

    return env->CallStaticIntMethod(cls, methodId, input);
}

extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "value from Singleton is %d", getValue(env, 3));
}

No such method error

When I remove the @JvmStatic annotation from Counter.getName(), I will get below error.

D/counter: Counter.getName is not found
A/art: art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: JNI CallStaticObjectMethodV called with pending exception java.lang.NoSuchMethodError: no static method "Lcom/practice/Counter;.getName()Ljava/lang/String;"

Android NDK. Calling Kotlin from native code

Call the method of nested class (also named static nested class in Java)

  • Counter.kt
class Counter {
    class Comparator(val num: Int) {
        fun isSameAs(num: Int): Boolean {
            return num == this.num
        }
    }
}
  • native-call-java-lib.cpp
jboolean isSameAs(JNIEnv *env, jclass cls, jobject obj, jint num) {
    jmethodID methodId = env->GetMethodID(cls, "isSameAs", "(I)Z");
    return env->CallBooleanMethod(obj, methodId, num);
}

extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
    // new inner class
    jclass cls = env->FindClass("com/practice/Counter$Comparator");
    if (cls == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find inner class.");
    }
    jmethodID constructId = env->GetMethodID(cls, "<init>", "(I)V");
    if (constructId == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class.");
    }
    jobject obj = env->NewObject(cls, constructId, 3);
    if (obj == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of inner class.");
    }

    // isSameAs 3
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "Is 3 same as the number in inner class? %d", isSameAs(env, cls, obj, 3));
}

Call the method of inner class (non-static nested class in Java)

Inner class can access the member of outer class.

  • Outer.kt

    package com.practice
    
    class Outer {
        private val bar: Int = 1
    
        inner class Inner {
            fun getBar() = bar
        }
    }
    
  • native-call-java-lib.cpp

    jobject createOuterInstance(JNIEnv *env) {
        jclass cls = env->FindClass("com/practice/Outer");
        if (cls == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find outer class.");
        }
    
        jmethodID constructId = env->GetMethodID(cls, "<init>", "()V");
        if (constructId == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of outer class.");
        }
    
        return env->NewObject(cls, constructId);
    }
    
    jobject createInnerInstance(JNIEnv *env, jclass innerCls, jobject outerObj) {
        jmethodID innerConstructId = env->GetMethodID(innerCls, "<init>", "(Lcom/practice/Outer;)V");
        if (innerConstructId == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class.");
        }
    
        return env->NewObject(innerCls, innerConstructId, outerObj);
    }
    
    jint getBar(JNIEnv *env, jclass cls, jobject obj) {
        jmethodID methodId = env->GetMethodID(cls, "getBar", "()I");
        return env->CallIntMethod(obj, methodId);
    }
    
    extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "test()");
    
        // New Outer class
        jobject obj = createOuterInstance(env);
        if (obj == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of outer class.");
        }
    
        // New Inner class
        jclass innerCls = env->FindClass("com/practice/Outer$Inner");
        if (innerCls == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find inner class.");
        }
    
        jobject innerObj = createInnerInstance(env, innerCls, obj);
        if (innerObj == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of inner class.");
        }
    
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "value in inner class is %d", getBar(env, innerCls, innerObj));
    }
    

No such method error

D/native: can not get construct of inner class.
A/zygote: java_vm_ext.cc:504] JNI DETECTED ERROR IN APPLICATION: JNI NewObjectV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/practice/Outer$Inner;.<init>()V"
  • native-call-java-lib.cpp

    jobject createInnerInstance(JNIEnv *env, jclass innerCls, jobject outerObj) {
        jmethodID innerConstructId = env->GetMethodID(innerCls, "<init>", "()V"); // wrong here!
        if (innerConstructId == nullptr) {
            __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class.");
        }
    
        return env->NewObject(innerCls, innerConstructId); // also wrong here!
    }
    

I can use javap to check the signature of inner class constructor.

$ javap -s -p com/practice/Outer\$Inner.class 
Compiled from "Outer.kt"
public final class com.practice.Outer$Inner {
  final com.practice.Outer this$0;
    descriptor: Lcom/practice/Outer;
  public final int getBar();
    descriptor: ()I

  public com.practice.Outer$Inner();
    descriptor: (Lcom/practice/Outer;)V // That's it!
}

It still has the second wrong.

E/zygote: JNI ERROR (app bug): accessed stale Global 0xd67b6e72  (index 224900839 in a table of size 507)
A/zygote: java_vm_ext.cc:504] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0xd67b6e72

Inner object will hold the reference of outer object, so I have to pass the outer instance to the constructor of inner class even it has no parameter.

Nested Classes of The Java™ Tutorials @Oracle

Static inner class in Kotlin @StackOverflow

Nested and inner class @Kotlin Official Side

results matching ""

    No results matching ""