Use of android JNI

jni technology is used to realize the connection between java and c/c + + code, and is widely used in android platform, such as

  • Start the java virtual machine with local code and create the java running environment. For example, create the java virtual machine after the process zygote starts
  • System services are mostly implemented in c/c + +, in which jni provides interfaces for java code calls

The following mainly describes the usage of jni in common scenarios, such as how Java applications call local library functions, how local library functions call java code, and how to create Java virtual machines in c programs to execute java code. At the same time, it also mentioned how to register jni local functions and establish the mapping between Java local methods and local library functions to speed up the operation efficiency.

Calling c library function in java

  1. Write java code, in which the methods that need to be implemented locally are modified with native
        class Hello{
            native void printHello();
            static {
                System.loadLibrary("Hello");
            }
            public static void main(String[] args){
                Hello hello = new Hello();
                hello.printHello();
            }
        }
  1. Compile java files into class files
        $ javac Hello.java
  1. Generate c language header file according to java file
        $ javah Hello

The corresponding c language header file hello h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_printHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

The automatically generated local function names are regular, and the JNIEXPORT JNICALL keyword is in JNI The macro registered in the H header file. The function name format is java_ Class name_ Local method name, so that the system can find the local function corresponding to the java local method when loading the local function library.

The generated function prototype has two default parameters. The first JNIEnv * type parameter is the pointer of the jni interface, which is used to call various functions in the jni table, and the second parameter jobject type parameter refers to the reference of the object calling the local method. If the java Native method is of type static, the second parameter is of type jclass.
4. Write c code, hello c
Just implement the method in the header file. Pay attention to adding the parameter name

        #include "Hello.h"
        #include <stdio.h>

        /*
         * Class:     Hello
         * Method:    printHello
         * Signature: ()V
         */
        JNIEXPORT void JNICALL Java_Hello_printHello
          (JNIEnv *env, jobject obj){
            printf("Hello World!\n");
        }
  1. Generate shared library
        $ gcc -I/usr/lib/jvm/java-7-oracle/include -I/usr/lib/jvm/java-7-oracle/include/linux -fPIC -shared -o libHello.so Hello.c
Use here-I Option specifies the header file jni.h Location, final compilation generation libHello.so File.
  1. Run the java program to view the results
        $ java -Djava.library.path=. Hello
        Hello World!

Here, hello Class class needs to specify the path of the dynamic library with the - D option.

Calling java code in c library function

First write a main Java class, which will call the local library libmain The library function in so, in which the following operations are demonstrated:

  • Creating java objects
  • Accessing java class static variables
  • Accessing static methods of java classes
  • Accessing variables of java objects
  • Methods of accessing java objects
  1. Write the Main class. The code is very simple, that is, call the c library function
        class Main{
            static native void main();
            public static void main(String args[]) {
                System.loadLibrary("main");
                main();
            }
        }
  1. Generate main according to the javah tool H file to implement local functions

    We need to be in main C file to implement jnical java declared in the header file_ Main_ Main method. Since the corresponding java method is modified by static, the second function parameter here is of jclass type.

        #include "Main.h"
        #include <stdio.h>

        JNIEXPORT void JNICALL Java_Main_main(JNIEnv *env, jclass clazz){

            // Find JniTest class
            jclass targetClass = (*env)->FindClass(env, "JniTest"); 
            // Find the constructor of the JniTest class
            jmethodID constructorId = (*env)->GetMethodID(env, targetClass, "<init>", "(I)V");
            // 1. Generate JniTest object
            jobject newObject = (*env)->NewObject(env, targetClass, constructorId, 100);
            printf("initiate JniTest object.\n");
            
            // 2. Get the static variable of JniTest class
            jfieldID staticFieldId = (*env)->GetStaticFieldID(env, targetClass, "var1", "I");
            jint var1 = (*env)->GetStaticIntField(env, targetClass, staticFieldId);
            // 3. Get the common variable of JniTest class
            jfieldID fieldId = (*env)->GetFieldID(env, targetClass, "var2", "I");
            jint var2 = (*env)->GetIntField(env, targetClass, fieldId);
            printf("var1: %d , var2: %d\n", var1, var2);
            
            // 4. Call the static method of JniTest class
            jmethodID staticMethodId = (*env)->GetStaticMethodID(env, targetClass, "getVar1",  "()I");
            jint var11 = (*env)->CallIntMethod(env, newObject, staticMethodId);
            // 5. Call the common method of JniTest class
            jmethodID methodId = (*env)->GetMethodID(env, targetClass, "getVar2",  "()I");
            jint var22 = (*env)->CallIntMethod(env, newObject, methodId);
            printf("var1: %d , var2: %d\n", var11, var22);

        }
  1. Compile and run effect
        $ javac Main.java
        $ gcc -I/usr/lib/jvm/java-7-oracle/include -I/usr/lib/jvm/java-7-oracle/include/linux -fPIC -shared -o libmain.so Main.c
        $ java -Djava.library.path=. Main
        initiate JniTest object.
        var1: 1 , var2: 0
        var1: 1 , var2: 100

As can be seen from the above code, the Jni interface pointer defines a series of methods for finding class es, instantiating object s, finding IDS, obtaining / setting field s, and calling methods, such as

    jclass FindClass(JNIEnv *env, const char *name)
    jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...)
    jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
    jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
    <jnitype> Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)
    void Set<Type>Field(JNIEnv *env, jobject obj, jfeildID feildID, <type> value)
    <jnitype> Call<type>Method(JNIEnv *env, jobject obj, jmethodId methodID, ...)

When calling jni functions, c is slightly different from c + +, for example

    jclass clazz = (*env)->FindClass(env, "JniTest");   // c Style
    jclass clazz = env->FindClass("JniTest");   // c + + style

When looking for the id, you need to pass in the java member variable or the signature of the method to describe the return value and parameters of the method, which can be obtained through the javap command

    $javap -s -p JniTest
    Compiled from "JniTest.java"
    class JniTest {
      private static int var1;
        Signature: I
      private int var2;
        Signature: I
      public JniTest(int);
        Signature: (I)V

      public static int getVar1();
        Signature: ()I

      public void setVar2(int);
        Signature: (I)V

      public int getVar2();
        Signature: ()I

      static {};
        Signature: ()V
    }

Creating java running environment in c program

jni provides a set of Invocation api, which allows c/c + + programs to load java virtual machine in their own memory area and create java running environment. In android system, zygote process (i.e. app_process) starts the first java virtual machine in this way.

First write the local program invocation, whose code is invocaiotn C as follows

    #include <jni.h>

    int main() {
        JNIEnv *env;
        JavaVM *vm;
        JavaVMInitArgs vm_args;
        JavaVMOption options[1];
        jint res;
        jclass cls;
        jmethodID mid;
        jstring jstr;
        jclass stringClass;
        jobjectArray args;

        // 1. Generate java virtual machine option
        options[0].optionString = "-Djava.class.path=.";
        vm_args.version = JNI_VERSION_1_2;
        vm_args.options = options;
        vm_args.nOptions = 1;
        vm_args.ignoreUnrecognized = JNI_TRUE;
        
        // 2. Generate java virtual machine
        res = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
        
        // 3. Find java classes
        cls = (*env)->FindClass(env, "Demo");
        
        // 4. Get the main method id in the java class
        mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
        
        // 5. Generate a string object as a parameter of the main method
        jstr = (*env)->NewStringUTF(env, "Hello World!");
        stringClass = (*env)->FindClass(env, "java/lang/String");
        args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
        
        // 6. Call the main method
        (*env)->CallStaticVoidMethod(env, cls, mid, args);
        
        // 7. Destroy the virtual machine
        (*vm)->DestroyJavaVM(vm);
    }

JNI is required for compilation H header file and link libjvm So library. The specific compilation instructions are as follows

    $ gcc -o invocation invocation.c -I/usr/lib/jvm/java-7-oracle/include -I/usr/lib/jvm/java-7-oracle/include/linux -L/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server -ljvm

Note that when gcc processes symbolic links, if it finds that the library file is not used by the left parameter when processing the - l parameter, it will ignore the - l parameter, so it is best to put the - l parameter last to avoid link errors [undefined reference to `JNI_CreateJavaVM 'Linux]( http://stackoverflow.com/questions/16860021/undefined-reference-to-jni-createjavavm-linux )

The following are the java classes to be called

    class Demo {
        public static void main(String args[]) {
            System.out.println(args[0]);
        }
    }

After compiling the java class, we can execute the invocation program to see the effect

    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server
    $ ./invocation
    Hello World!

Register jni local function in c program

When running a java application containing local methods, the java virtual machine needs to go through the following steps

  • Call system Loadlibrary() method to load the runtime into memory
  • The java virtual machine retrieves the library function symbols loaded in and finds the JNI local function symbols with the same signature as the java local method. If a consistent is found, the local method is mapped to a specific JNI local function.

The jni mechanism provides a jni function named registernatives (), which allows c/c + + developers to directly map jni local functions and local methods of java classes to improve operation efficiency.

  1. Register jni local functions when loading local libraries

    Calling System. in java code When using the loadlibrary() method, the java virtual machine will retrieve the function symbols in the shared library and check jni after loading the specified shared library_ Whether the onload() function is implemented or not. If yes, it will be called. Otherwise, the local method will be automatically compared and matched with the jni local function symbol in the shared library.

    jni_ The most basic function of onload() function is to determine the jni version supported by the java virtual machine, so this function must return information about the jni version. The following code rewrites jni for local code_ Onload() method

        #include <jni.h>
        #include <stdio.h>

        // Implement local functions
        void printHelloNative() {
            printf("Hello World!\n");
        }

        JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
            JNIEnv* env = NULL;
            JNINativeMethod nm[1];
            jclass cls;
            
            if((*vm)->GetEnv(vm, (void*)&env, JNI_VERSION_1_4) != JNI_OK) {
                printf("Error!");
                return JNI_ERR;
            }
            
            cls = (*env)->FindClass(env, "Hello");
            nm[0].name = "printHello";
            nm[0].signature = "()V";
            nm[0].fnPtr = (void *)printHelloNative;
            
            // Put hello The printHello method in the Java class is bound to the local function printHelloNative
            (*env)->RegisterNatives(env, cls, nm, 1);
            
            return JNI_VERSION_1_4;
        }

Compile as libmain. Exe with the following command So library file

        $ gcc -fpic -shared -o libmain.so Main.c -I/usr/lib/jvm/java-7-oracle/include -I/usr/lib/jvm/java-7-oracle/include/linux

    As follows Hello.java class

        class Hello {
            native static void printHello();
            static { System.loadLibrary("main"); }
            
            public static void main(String args[]) {
                printHello();
            }
        }

After compiling and running, you can see that the local function is found successfully

        $ java -Djava.library.path=. Hello
        Hello World!
  1. Register jni local function in c program

    If developers call java code through the invocation api in c language, they do not have to rewrite JNI_OnLoad() method, you can directly call RegisterNatives() method to complete the mapping between JNI local functions and JNI local methods.

    In the zygote process startup process in the Android system (implemented by calling the AndroidRuntime::start method of frameworks/base/core/jni/AndroidRuntime.cpp by calling the main method in frameworks/base/cmds/app_prosss/app_main.cpp), after starting the vm, the AndroidRuntime::startReg(JNIEnv* env) method will be called first to register various local methods used in the android framework before calling com android. internal. os. ZygoteInit. Main method of Java class.

    By tracing the startReg method, you can see that the mapping between local functions and java local methods is finally established through code similar to the following

        int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
        {
            return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
                gMethods, NELEM(gMethods));
        }

The jniRegisterNativeMethods method is located in libnativehelper / jnihelp In CPP, the registration is finally completed by calling the RegisterNatives method.

        extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
            const JNINativeMethod* gMethods, int numMethods)
        {
            // ...
            
            if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
                char* msg;
                asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
                e->FatalError(msg);
            }

            return 0;
        }

Keywords: Java Android

Added by naggi on Fri, 17 Dec 2021 00:48:08 +0200