Typically we use JNI, where the corresponding interface methods in the Java layer have corresponding method implementations in the native code.

Resolution by item name through dynamic linker

Entries are resolved by their name through a dynamic linker. Local method names are concatenated from the following components:

  • The prefix Java_
  • A fully qualified class name
  • Underscore (” _ “) separator
  • A method name
  • For overloaded local methods, the two underscores (” __ “) are followed by the parameter signature

The VM checks whether the method name matches the method residing in the local library. VM first looks for the short name; That is, no parameter signature name. It then looks for the long name with the parameter signature. Programmers need to use long names only if a local method is overloaded by another local method. However, if a local method has the same name as a non-local method, this is not a problem. Non-native methods (Java methods) do not reside in local libraries.

The sample code is compiled by Android Studio.

There is no overloaded version of the 1.1 method

package com.example.ndk.ndkdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
Copy the code

The corresponding implementation of the Java layer stringFromJNI() method is actually the following method in the native library (Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(…)) ) :

#include <jni.h>
#include <string>

jstring NewStringUTF(JNIEnv *env, const char *str) {
    return env->NewStringUTF(str);
}

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

The local methods use a Java_ prefix followed by a fully qualified Java class name (com_example_ndk_ndkdemo_MainActivity) separated by an underscore (_), followed by a stringFromJNI string with the same name as the Java method. Since we only have one method called stringFromJNI, the VM first looks for the short name (that is, the name without the parameter signature) to find the corresponding local method implementation.

1.2 Methods contain overloaded versions

In the absence of a short name, the VM looks for a long name with a parameter signature. Programmers need to use long names only if a local method is overloaded by another local method. If there is an overloaded version of a function that does not use a long name, the short-named method will be called and the overloaded version of the function cannot be executed as expected. Let’s define a JNI method named stringFromJNI, which must have a local method name with two underscores (” __ “) followed by the parameter signature.

package com.example.ndk.ndkdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI("hello"));
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    public native String stringFromJNI(String str);
}
Copy the code

Overloaded versions of JNI methods are defined, and local methods must have long names.

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2(
        JNIEnv *env,
        jobject /* this */,
        jstring jstr) {
    std::string hello = "Hello from C++ with param";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

In Java, the stringFromJNI() method has an empty parameter signature, so the native code is written as Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__, followed by just two underscores (__), In Java, the stringFromJNI(String STR) method corresponds to Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2. We know that the String class corresponds to the Ljava/lang/String signature; , replace/with _, while “;” It needs to be escaped, it gets escaped to _2, that’s why it’s followed by _2.

2. Call RegisterNatives() to register the local method

Programmers can also call the JNI function RegisterNatives() to register local methods associated with the class. The RegisterNatives() function is particularly useful for statically linked functions.

2.1 involve the API

2.1.1 RegisterNatives

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
Copy the code

Register local methods with the class specified by the Clazz parameter. The methods argument specifies an array of JNINativeMethod structures that contain the name, signature, and function pointer of the local method. The name and signature fields of the JNINativeMethod structure are Pointers to the modified UTF-8 string. The nMethods argument specifies the number of local methods in the array. JNINativeMethod structure is defined as follows:

typedef struct { 

    char *name; 

    char *signature; 

    void *fnPtr; 

} JNINativeMethod; 
Copy the code

Function Pointers must nominally have the following signature:

ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...) ;Copy the code

LINKAGE:

Index 215 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

Clazz: a Java class object.

Methods: local methods in a class.

NMethods: The number of local methods in a class.

RETURNS:

Returns “0” on success; Returns a negative value on failure.

THROWS:

NoSuchMethodError: if the specified method cannot be found or the method is not local.

2.1.2 JNI_OnLoad

jint JNI_OnLoad(JavaVM *vm, void *reserved);
Copy the code

When the local library is loaded, the VM calls JNI_OnLoad(for example, through System.loadLibrary). JNI_OnLoad must return the VERSION of JNI required by the local library.

In order to use any new JNI functions, the local library must export the JNI_OnLoad function that returns JNI_VERSION_1_2. If the local library does not export the JNI_OnLoad function, the VM assumes that the library only needs the JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the local library cannot be loaded.

LINKAGE:

Export from the local library that contains the implementation of the local method.

SINCE:

The JDK/JRE 1.4

In order to use the JNI functions introduced in J2SE version 1.2, in addition to those provided in JDK/JRE 1.1, the local library must export a JNI_OnLoad function that returns JNI_VERSION_1_2.

In order to use the JNI functions introduced in J2SE release 1.4, the local library must export the JNI_OnLoad function that returns JNI_VERSION_1_4, in addition to the functions provided in release 1.2.

If the local library does not export the JNI_OnLoad function, the VM assumes that the library only needs the JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the local library cannot be loaded.

2.2 instance

Continuing with the above example, the Java layer code does not remain unchanged and the native code changes as follows:

#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL stringFromJNI1( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jstring JNICALL stringFromJNI2( JNIEnv *env, jobject /* this */, jstring jstr) { std::string hello = "Hello from C++ with param"; return env->NewStringUTF(hello.c_str()); } static JNINativeMethod methods[] = { {"stringFromJNI", "()Ljava/lang/String;" , reinterpret_cast<void *>(stringFromJNI1)}, {"stringFromJNI", "(Ljava/lang/String;) Ljava/lang/String;" , reinterpret_cast<void *>(stringFromJNI2)} }; JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if (JNI_OK ! = vm->GetEnv(reinterpret_cast<void **> (&env), JNI_VERSION_1_4)) { return JNI_ERR; } jclass clazz = env->FindClass("com/example/ndk/ndkdemo/MainActivity"); Java natives if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof((methods)[0])) < 0) { return JNI_ERR; } return JNI_VERSION_1_4; }Copy the code

In the JNI_OnLoad method we registered the local method, mainly calling the RegisterNatives method to avoid the long method name of the local method. Reinterpret_cast <void*>(stringFromJNI1) and reinterpret_cast<void*>(stringFromJNI2) are local implementation method names, which are cast to function Pointers.