JNI

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html

https://developer.android.com/training/articles/perf-jni

JavaVM and JNIEnv

图解

graph LR
JavaVM-->singlePerProcess
JNIEnv-->singlePerThread

JNI defines two key data structures, “JavaVM” and “JNIEnv”. Both of these are essentially pointers to pointers to function tables. (In the C++ version, they’re classes with a pointer to a function table and a member function for each JNI function that indirects through the table.) The JavaVM provides the “invocation interface” functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but Android only allows one.

The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument.

The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. If a piece of code has no other way to get its JNIEnv, you should share the JavaVM, and use GetEnv to discover the thread’s JNIEnv. (Assuming it has one; see AttachCurrentThread below.)

Native libraries

You can load native code from shared libraries with the standard System.loadLibrary.

In practice, older versions of Android had bugs in PackageManager that caused installation and update of native libraries to be unreliable. The ReLinker project offers workarounds for this and other native library loading problems.

To use RegisterNatives:

  • Provide a JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) function.
  • In your JNI_OnLoad, register all of your native methods using RegisterNatives.
  • Build with -fvisibility=hidden so that only your JNI_OnLoad is exported from your library. This produces faster and smaller code, and avoids potential collisions with other libraries loaded into your app (but it creates less useful stack traces if you app crashes in native code).

https://android-developers.googleblog.com/2011/07/debugging-android-jni-with-checkjni.html

https://developer.android.com/training/articles/perf-jni#native-libraries

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

https://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp615

The programmer can also call the JNI function RegisterNatives() to register the native methods associated with a class. The RegisterNatives() function is particularly useful with statically linked functions.

Resolving Native Method Names Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:

  • the prefix Java_
  • a mangled fully-qualified class name
  • an underscore (“_”) separator
  • a mangled method name
  • for overloaded native methods, two underscores (“__”) followed by the mangled argument signature

The VM checks for a method name match for methods that reside in the native library. The VM looks first for the short name; that is, the name without the argument signature. It then looks for the long name, which is the name with the argument signature. Programmers need to use the long name only when a native method is overloaded with another native method. However, this is not a problem if the native method has the same name as a nonnative method. A nonnative method (a Java method) does not reside in the native library.

In the following example, the native method g does not have to be linked using the long name because the other method g is not a native method, and thus is not in the native library.

class Cls1 { 
  int g(int i); 
  native int g(double d); 
}

Native Method Arguments

The JNI interface pointer is the first argument to native methods. The JNI interface pointer is of type JNIEnv. The second argument differs depending on whether the native method is static or nonstatic. The second argument to a nonstatic native method is a reference to the object. The second argument to a static native method is a reference to its Java class.

The remaining arguments correspond to regular Java method arguments. The native method call passes its result back to the calling routine via the return value. Chapter 3 describes the mapping between Java and C types.

package pkg;  

class Cls { 
     native double f(int i, String s); 
     ... 
} 

//The C function with the long mangled name Java_pkg_Cls_f_ILjava_lang_String_2 implements native method f:
//Code Example 2-1 Implementing a Native Method Using C

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( 
     JNIEnv *env,        /* interface pointer */ 
     jobject obj,        /* "this" pointer */ 
     jint i,             /* argument #1 */ 
     jstring s)          /* argument #2 */ 

{ 
     /* Obtain a C-copy of the Java string */ 
     const char *str = (*env)->GetStringUTFChars(env, s, 0); 

     /* process the string */ 
     ... 

     /* Now we are done with str */ 
     (*env)->ReleaseStringUTFChars(env, s, str); 
     return ... 
}

Referencing Java Objects

  • Primitive types, such as integers, characters, and so on, are copied between Java and native code.

  • Arbitrary Java objects, on the other hand, are passed by reference.

The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the garbage collector. The native code, in turn, must have a way to inform the VM that it no longer needs the objects. In addition, the garbage collector must be able to move an object referred to by the native code.

Global and Local References

The JNI divides object references used by the native code into two categories: local and global references.

  • Local references are valid for the duration of a native method call, and are automatically freed after the native method returns.
  • Global references remain valid until they are explicitly freed.

Implementing Local References

To implement local references, the Java VM creates a registry for each transition of control from Java to a native method. A registry maps nonmovable local references to Java objects, and keeps the objects from being garbage collected. All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry. The registry is deleted after the native method returns, allowing all of its entries to be garbage collected.

注册方式JNI_OnLoad

extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
}

C调用java方法

static jmethodID g_callbackOnContentChange = nullptr;

    g_cls = reinterpret_cast<jclass>(env->NewGlobalRef(instance));
    if (!g_cls) {
        MMKVError("fail to create global reference for %s", clsName);
        return -3;
    }

    g_callbackOnContentChange =
        env->GetStaticMethodID(g_cls, "onContentChangedByOuterProcess", "(Ljava/lang/String;)V");
    if (!g_callbackOnContentChange) {
        MMKVError("fail to get method id for onContentChangedByOuterProcess()");
    
currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentChange, str);

https://source.android.com/devices/tech/debug/native-memory

java调用C,异常捕获

Within the JNI literature, the word exception appears to be used exclusively to refer to Java exceptions. Unexpected events in native code are referred to as programming errors. JNI explicitly does not require JVMs to check for programming errors. If a programming error occurs, behavior is undefined. Different JVMs may behave differently.

It’s the native code’s responsibility to translate all programming errors into either return codes or Java exceptions. Java exceptions don’t get thrown immediately from native code. They can be pending, only thrown once the native code returns to the Java caller. The native code can check for pending exceptions with ExceptionOccurred and clear them with ExceptionClear.

//libnativehelper/include/nativehelper/JNIHelp.h
inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
    return jniThrowRuntimeException(&env->functions, msg);
}

//libnativehelper/JNIHelp.c
int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
    return jniThrowException(env, "java/lang/RuntimeException", msg);
}

int jniThrowException(JNIEnv* env, const char* className, const char* message) {
    DiscardPendingException(env, className);
    jclass exceptionClass = (*env)->FindClass(env, className);
    if (exceptionClass == NULL) {
        ALOGE("Unable to find exception class %s", className); /* ClassNotFoundException now pending */
        return -1;
    }
    int status = 0;
    if ((*env)->ThrowNew(env, exceptionClass, message) != JNI_OK) {
        ALOGE("Failed throwing '%s' '%s'", className, message);
        /* an exception, most likely OOM, will now be pending */
        status = -1;
    }
    (*env)->DeleteLocalRef(env, exceptionClass);
    return status;
}

//art/runtime/jni/jni_internal.cc
  static jint ThrowNew(JNIEnv* env, jclass c, const char* msg) {
    return ThrowNewException(env, c, msg, nullptr);
  }

int ThrowNewException(JNIEnv* env, jclass exception_class, const char* msg, jobject cause)
  REQUIRES(!Locks::mutator_lock_) {
  jmethodID mid = env->GetMethodID(exception_class, "<init>", signature);
  ScopedLocalRef<jthrowable> exception(
      env, reinterpret_cast<jthrowable>(env->NewObjectA(exception_class, mid, args)));
  ScopedObjectAccess soa(env);
  soa.Self()->SetException(soa.Decodemirror::Throwable(exception.get()));
}


//art/runtime/thread.cc
void Thread::SetException(ObjPtrmirror::Throwable new_exception) {
  CHECK(new_exception != nullptr);
  // TODO: DCHECK(!IsExceptionPending());
  tlsPtr_.exception = new_exception.Ptr();
}

参考«OKHttp读取Socket流程.vsdx»中系统对recvFrom jni调用的异常向java层抛出

C调用java,异常捕获

if you invoke a Java method from JNI, calling ExceptionCheck afterwards will return JNI_TRUE if an exception was thrown by the Java.

if you’re just invoking a JNI function (such as FindClass), ExceptionCheck will tell you if that failed in a way that leaves a pending exception (as FindClass will do on error).