Through this article, you should achieve the following goals:

  1. Understand what JNI is, how IT relates to Java, and how it relates to the JVM.
  2. Learn how Java calls JNI and how to implement Java native methods through C/C ++.
  3. Learn how to call Java methods within JNI.

Macos c/c++ project construction reference: macos+vscode+cmake build c/c++ project

1 basis

1.1 What is JNI

JNI, which stands for Java Native Interface, ensures that code is portable across platforms by writing programs using the Java Native Interface. Since Java1.1, the JNI standard has become part of the Java platform, allowing Java code to interact with code written in other languages.

This article focuses on how Java interacts with C or C++ through JNI.

1.2 JNI data types

JNI acts as a mediator between Java and C/C++. Considering that the data types between Java and C/C++ languages are not uniform, JNI has its own set of data types corresponding to Java and C/C++ respectively, which is a set of intermediate data types.

Java JNI describe
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat signed 32 bits
double jdouble signed 64 bits
Class jclass Class class object
String jstring String object
Object jobject Any Java object
byte[] jbyteArray Byte array

1.3 JNI Context

The JNIEnv type actually represents the Java environment, and JNI functions can operate on Java-side code through JNIEnv* Pointers. For example, create objects of Java classes, call methods of Java objects, get properties of objects, and so on. Pointers to JNIEnv are passed into native methods on the JNI side to manipulate java-side code. Such as:

jstring f1(JNIEnv *env, jclass jobj){
    return (*env)->NewStringUTF(env,"Hi Java");
}
Copy the code

To distinguish jobject from jclass: in JNI side statement Java native method implementation, there are two default parameters (except native methods own incoming parameters), were the JNIEnv pointer, another was a jobject/jclass, the difference between the two: Jobject: If the Native method on the Java side is non-static, then the second argument passed to JNI is the object of the class, all of which are of type Jobject on the JNI side. Jclass: If the Native method on the Java side is static, the second argument passed to JNI is the runtime class of the class, all of which are of type JClass on the JNI side.

JNIEXPORT and JNICALL: JNIEXPORT and JNICALL are two keywords that indicate that the method is a JNI method.

JNIEXPORT jint JNICALL Java_register_staticRegister_StaticRegister_func
(JNIEnv *env, jclass jobj,jintArray arr){
    int len = (*env)->GetArrayLength(env,arr);
    return len;
}
Copy the code

1.4 Java signature

View signatures for Java fields and methods

Javap command: javap -s -p JniTest. Class

Java signature specification

In the JNI standard, Java method signatures have the following convention.

The data type The signature
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
object Start with L, then delimit the full type of the package with a ‘/’, and end with ‘; ‘(semicolon).

Such as:

String signature is Ljava/lang/String;
Array [begins, followed by the signature of the element type.

Such as:

Int [] is signed by [I; int[] is signed by [[I; int[] is signed by [].

Object [] the signature is [Ljava/lang/ object;

For example,

//Java:
import java.util.Data;
public class Hello {
    public int property;
    public int function(int fu,Date date,int[] arr) {
        return 0;
    }
    public native void test(a);
}
Copy the code
//C++:
JNIEXPORT void JNICALL Java_Hello_test(JNIEnv *env, jobject jobj){
    jclass hello_clz = (*env) -> GetObjectClass(env, jobj);
    jfieldID fieldID_prop = (*env) -> GetFieldID(env, hello_claz, "property"."I");
    jmethodID methodID_func = 
        (*env) -> GetMethodID(env, hello_clz, "function"."(ILjava/util/Date; [I)I");
    (*env) -> CallIntMethod(env, jobj, methodID_func, 0L.NULL.NULL);
}
Copy the code

2 Java invokes JNI

After Java defines native methods, JNI implements native methods in detail. After Java definition is associated with JNI implementation, Java can directly call native methods to use JNI implementation. The above association operations are also known as registrations, in other words, registrations are the operations necessary to associate JNI functions with Java Native methods, and registrations are classified as static and dynamic registrations.

2.1 Static Registration

Steps:

  • Write a Java class, such as Hello.java;
package learn.java.jni;

public class Hello {

    public static native String jniSaysHello(a);

    public static String javaSaysHello(a) {
        return "Java: Hello JNI, this is Java!";
    }

    public static void main(String[] args) { System.out.println(javaSaysHello()); System.out.println(jniSaysHello()); }}Copy the code
  • In the. Java source file directory, enter javac hello. Java to generate the hello. class file.
  • In the directory where the hello. class package resides, run javah learn.java.Hello (complete class name without suffix) to generate the learn_JAVA_jni_hello. h header file.
  • For JDK 1.8 or above, the above steps can be simplified into one step: In the hello. Java directory, run javac -h. hello. Java command line to get.

  • Create a C/C++ project and copy the learn_JAVA_jni_hello. h file to the project directory.
  • Add jni.h and jni_md.h headers to your C/C++ project. These two header files are included with the JDK.
  • Change #include

    to #include “jni.h” in learn_java_jni_Hello.
  • As you can see, the learn_JAVa_jni_hello. h file contains a JNI declaration of a Java aspect native method, JNICALL Keyword two Java_learn_java_jni_Hello_jniSaysHello Java_ Full class name _ Method name (JNIEnv *, jclass); The following;
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class learn_java_jni_Hello */

#ifndef _Included_learn_java_jni_Hello
#define _Included_learn_java_jni_Hello
#ifdef __cplusplus
extern "C"
{
#endif
  /* * Class: learn_java_jni_Hello * Method: jniSaysHello * Signature: ()Ljava/lang/String; * /
  JNIEXPORT jstring JNICALL Java_learn_java_jni_Hello_jniSaysHello(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif
Copy the code
  • # learn_javA_jni_hello. h # learn_java_jni_hello. h # learn_java_jni_hello. h # learn_java_jni_Hello.
#include "learn_java_jni_Hello.h"

JNIEXPORT jstring JNICALL Java_learn_java_jni_Hello_jniSaysHello(JNIEnv *env, jclass jc)
{
    return (*env)->NewStringUTF(env, "JNI: Hello Java, this is JNI!");
}
Copy the code
  • Compile the cmakelists.txt file, which is the configuration file for the library. Most important are the last two add_library(); the rest are generated automatically. Add_library () declares the name, type, and contained.c&.h files of the library. DLL (Windows)/.dylib(MAC). STATIC (Windows & MAC). Note: the library itself has dynamic library and static library, Java Native methods also have static registration and dynamic registration, the two are not related. I’ll call the dynamic library JniSaysHello;

The difference between dynamically and statically linked libraries

cmake_minimum_required(VERSION 3.0.0)
project(jnicppdemo VERSION 0.1.0)

include(CTest)
enable_testing(a)add_library(JniSaysHello SHARED learn_java_jni_Hello.c learn_java_jni_Hello.h)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
Copy the code
  • At this point, the C/C++ Project structure is shown as follows. Build Project generates the dynamic link library and obtains libjnisayshello.dylib.

  • On the Java side, add a static code block to the hello.java source file and use the system.load () method to load the dynamic link library as follows:

package learn.java.jni;

public class Hello {

    static {
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniSaysHello.dylib");
    }

    public static native String jniSaysHello(a);

    public static String javaSaysHello(a) {
        return "Java: Hello JNI, this is Java!";
    }

    public static void main(String[] args) { System.out.println(javaSaysHello()); System.out.println(jniSaysHello()); }}Copy the code
  • Java successfully called the method in dylib, static registration is successful.

  • JNI uses Java_PACKAGENAME_CLASSNAME_METHODNAME to match methods on the Java side, which is static registration.

2.2 Dynamic Registration

Steps:

  • Write a Java class, such as Hello.java, as follows;
package learn.java.jni;

public class Hello {

    static {
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniSaysHello.dylib");
    }

    private static final int[] array = {1.2.3};

    public static native String jniSaysHello(a);

    private static native String jniHowdy(a);

    protected static native String jniCalculateArrayLength(int[] array);

    public static String javaSaysHello(a) {
        return "Java: Hello JNI, this is Java!";
    }

    public static String javaHowdy(a) {
        return "Java: How are you doing?";
    }

    public static String javaDoNotKnowArrayLenth(a) {
        return "Java: Please help me calculate length of this array.";
    }

    public static void main(String[] args) {
        // static register
        System.out.println(javaSaysHello());
        System.out.println(jniSaysHello());
        // dynamic registerSystem.out.println(javaHowdy()); System.out.println(jniHowdy()); System.out.println(javaDoNotKnowArrayLenth()); System.out.println(jniCalculateArrayLength(array)); }}Copy the code
  • Add jni.h and jni_md.h headers to your C/C ++ project. These two header files are included with the JDK.
  • Create the C/C++ source file dynamic_register.c. In the.c file, implement two functions that will be JNI implementations of native methods, as follows:
#include "jni.h"

jstring howdy(JNIEnv *env, jclass jcls)
{
    return (*env)->NewStringUTF(env, "JNI: I am fine, how are you?");
}

jstring calculate_array_len(JNIEnv *env, jclass jcls, jintArray arr)
{
    int len = (*env)->GetArrayLength(env, arr);
    char lenStr[100];
    sprintf(lenStr, "JNI: Length of this array is %d.", len);
    return (*env)->NewStringUTF(env, lenStr);
}
Copy the code
  • So far, JavajniHowdyMethod,jniCalculateArrayLengthMethods with the c/c + +howdyThe function,calculate_array_lenThere’s no connection yet. We needManually Managing associations;
  • In dynamic_register.c, create an array of JNINativeMethod elements as follows:
static const JNINativeMethod mMethods[] = {
    {"jniHowdy"."()Ljava/lang/String;", (jstring *)howdy},
    {"jniCalculateArrayLength"."([I)Ljava/lang/String;", (jstring *)calculate_array_len},
};
Copy the code
  • Each element in the above array is the association between JNI side implementation method and Java side Native method. The first two are the description of Java side native method, and the last one is the description of JNI side function implementation, in the format:
{"Java side native method name"."Method signature", function pointer}Copy the code
  • Then, we need to implement the JNI_OnLoad() method in jni.h, which is implemented using a template as follows:
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env = NULL;
    / / get JNIEnv
    int r = (*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4);
    if(r ! = JNI_OK) {return - 1;
    }
    jclass mainActivityCls =
        (*env)->FindClass(env, "learn/java/jni/Hello");
    // The last parameter is the number of native methods that need to be registered. If less than 0, the registration fails.
    r = (*env)->RegisterNatives(env, mainActivityCls, mMethods, 2);
    if(r ! = JNI_OK) {return - 1;
    }
    return JNI_VERSION_1_4;
}
Copy the code
  • Pay attention to! First: aboveFindClass(env, "learn/java/jni/Hello")The string is the full class name of the Java side Hello class, note the backslash here/; The second:RegisterNatives(env, mainActivityCls, mMethods, 2)The last integer argument inNumber of methods that need to be dynamically registered, manually add registration or delete registration need corresponding changes, of course, you can directly putmMethod[]The length passed in, once and for all, is as follows:
int cnt = sizeof(mMethods)/ sizeof(mMethods[0]);
r = (*env)->RegisterNatives(env, mainActivityCls, mMethods, cnt);
Copy the code
  • Then, configure cmakelist.txt and add a new oneadd_library(JniHowdyAndCalculaterArrayLen SHARED dynamic_register.c)And thenCmake: Build:
cmake_minimum_required(VERSION 3.0.0)
project(jnicppdemo VERSION 0.1.0)

include(CTest)
enable_testing(a)# static register
add_library(JniSaysHello SHARED learn_java_jni_Hello.c learn_java_jni_Hello.h)

# dynamic register
add_library(JniHowdyAndCalculaterArrayLen SHARED dynamic_register.c)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
Copy the code
  • In the hello.java source file, add a static block of code to load the dynamic link library using the system.load () method as follows:
package learn.java.jni;

public class Hello {

    static {
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniSaysHello.dylib");
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniHowdyAndCalculaterArrayLen.dylib");
    }

    private static final int[] array = {1.2.3};

    public static native String jniSaysHello(a);

    private static native String jniHowdy(a);

    protected static native String jniCalculateArrayLength(int[] array);

    public static String javaSaysHello(a) {
        return "Java: Hello JNI, this is Java!";
    }

    public static String javaHowdy(a) {
        return "Java: How are you doing?";
    }

    public static String javaDoNotKnowArrayLength(a) {
        return "Java: Please help me calculate length of this array.";
    }

    public static void main(String[] args) {
        // static register
        System.out.println(javaSaysHello());
        System.out.println(jniSaysHello());
        // dynamic registerSystem.out.println(javaHowdy()); System.out.println(jniHowdy()); System.out.println(javaDoNotKnowArrayLength()); System.out.println(jniCalculateArrayLength(array)); }}Copy the code
  • Run on Java, the result is as follows:

  • Dynamic registration succeeded. Procedure

System.load () differs from system.loadLibrary() :

System.load()

The system.load () parameter must be the absolute path of the library file, for example: system.load (“C:\Documents\ testjni.dll “); //Windows System.load(“/usr/lib/TestJNI.dylib”); //mac

System.loadLibrary()

The system.loadLibrary () argument is the library file name and does not contain the library file extension. System.loadLibrary(“TestJNI”); // Load the testjni. DLL local library System. LoadLibrary (“TestJNI”); // Load the libtestjni. dylib local library on Linux

Note: Testjni.dll or libTestJni.dylib must be in the path pointed to by the JVM property java.library.path. LoadLibary needs to configure the java.library.path path for the current project

3 JNI invokes Java

3.1 JNI side to obtain the Java class runtime class

Distinguish FindClass() from GetObjectClass()

  • FindClass() : JNI side function, equivalent to class.forname () in Java, retrieves jCLASS variables available to JNI from the full Class name on the Java side.
jclass jclz = 
    (*env)->FindClass(env,"packageName/ClassName");
Copy the code
  • GetObjectClass() : JNI side function, equivalent to Object.getClass () on the Java side, obtains the corresponding JClass variable of jobject through reflection of the jobject object passed from Java to JNI side.
jclass jclz = (*env)->GetObjectClass(env,jobj);
Copy the code

3.2 JNI calls Java

Call the field

JNI has the corresponding GetXxx()/SetXxx() and GetStaticXxx()/SetStaticXxx() methods, which are divided into the following steps:

  1. JNI registration: Register native methods on Java side on JNI side;
  2. Get jclass: if you register a static native method, then the Jclass variable is passed directly into the JNI implementation and can be skipped to the next step. If you are registering a non-static native method, then the jobject variable is passed in the JNI implementation and you need to get the jclass using the GetObjectClass() method.
  3. Get jfieldID: call GetFieldID()/GetStaticFieldID();
  4. GetXxxField()/GetStaticXxxField();
  5. Action field: Call SetXxxField()/SetStaticXxxField().
// java
package learn.java.jni;

public class JNICallJavaField {

    static {
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniCallJavaField.dylib");
    }

    public int field1 = 1;

    public static int field2 = 8;

    public native void func1(a);// Field1 is not passed, but JNI is expected to modify field1

    public static native void func2(a);// Do not pass field2, but expect JNI to modify field2

    public static void main(String[] args) {
        JNICallJavaField jniCallJavaField = newJNICallJavaField(); jniCallJavaField.func1(); jniCallJavaField.func2(); System.out.println(jniCallJavaField.field1); System.out.println(jniCallJavaField.field2); }}Copy the code
// c/c++
#include "learn_java_jni_JNICallJavaField.h"

/* * Class: learn_java_jni_JNICallJava * Method: func1 * Signature: ()V */
JNIEXPORT void JNICALL Java_learn_java_jni_JNICallJavaField_func1(JNIEnv *env, jobject jobj)
{
    jclass jclz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldId = (*env)->GetFieldID(env, jclz, "field1"."I");
    jint field1 = (*env)->GetIntField(env, jobj, jfieldId);
    (*env)->SetIntField(env, jobj, jfieldId, field1 + 10000);
}

/* * Class: learn_java_jni_JNICallJava * Method: func2 * Signature: ()V */
JNIEXPORT void JNICALL Java_learn_java_jni_JNICallJavaField_func2(JNIEnv *env, jclass jclz)
{
    jfieldID jfieldId = (*env)->GetStaticFieldID(env, jclz, "field2"."I");
    jint field2 = (*env)->GetStaticIntField(env, jclz, jfieldId);
    (*env)->SetStaticIntField(env, jclz, jfieldId, field2 + 10000);
}
Copy the code
# CMakeList.txt
cmake_minimum_required(VERSION 3.0.0)
project(jnicppdemo VERSION 0.1.0)

include(CTest)
enable_testing(a)# static register
add_library(JniSaysHello SHARED learn_java_jni_Hello.c learn_java_jni_Hello.h)

# dynamic register
add_library(JniHowdyAndCalculaterArrayLen SHARED dynamic_register.c)

# jni call java field
add_library(JniCallJavaField SHARED learn_java_jni_JNICallJavaField.c)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
Copy the code

Java side effect:

A method is called

Note: Reflection cannot reflect abstract methods, nor is it necessary to reflect abstract methods

In JNI’s view, methods on the Java side are divided into constructor/static/non-static methods.

Java code side

Before going to the JNI side to call Java methods, let’s show the Java side code:

// a class unrelated to native methods, containing an empty parameter constructor, a nonstatic method, and a static method.
package learn.java.jni;

public class JNICallJavaMethod {

    public JNICallJavaMethod(a){
        System.out.println("Java: I am constructor.");
    }
    
    public int func(int i){
        System.out.println("Java: func is called.");
        return i+100;
    }
    
    public static int staticFunc(int[] a){
        System.out.println("Java: staticFunc is called.");
        int sum = 0;
        for(int i=0; i<a.length; i++){ sum += a[i]; }returnsum; }}Copy the code
// A class that contains native methods and a non-static method.
package learn.java.jni;

public class JNICallJavaMethodNative {

    static {
        System.load("/Users/tongbo/Documents/Projects/jni/jnic/jnicppdemo/build/libJniCallJavaMethod.dylib");
    }

    public native int func1(int i);

    public String func2(a) {
        System.out.println("Java: I am func2 in the same class with the native func1.");
        return "Java: Hi JNI.";
    }

    public static void main(String[] args) throws InterruptedException {
        JNICallJavaMethodNative jniCallJavaMethodNative = new JNICallJavaMethodNative();
        int res = jniCallJavaMethodNative.func1(8);
        /** * Test the sequence between the JNI side of the console print and the Java side of the console print: * It turns out that the JNI side of the console print must be after Java, sleep is not used. * /
        Thread.sleep(2000);
        System.out.println("Java: Result of native func1 is "+ res); }}Copy the code

As shown in the above code, we have two classes. The first class, JNICallJavaMethod, has no direct relationship with native methods. It provides constructors, non-static methods, and static methods for JNI to call. The second class, JNICallJavaMethodNative, contains declarations of native methods and a non-static method for JNI to call; At the same time, the second class JNICallJavaMethodNative provides PSVM as the entry point of the whole program. After the program starts running, only native methods are called on the Java side, and the following results are obtained:

Lateral JNI code

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

JNIEXPORT jint JNICALL Java_learn_java_jni_JNICallJavaMethodNative_func1(JNIEnv *env, jobject jobj, jint ji)
{

    // Create an int array on JNI
    jintArray jArr = (*env)->NewIntArray(env, 4);
    jint *arr = (*env)->GetIntArrayElements(env, jArr, NULL);
    arr[0] = 0;
    arr[1] = 10;
    arr[2] = 20;
    arr[3] = 30;
    (*env)->ReleaseIntArrayElements(env, jArr, arr, 0);

    // Call another class's constructor
    jclass jclz1 = NULL;
    jclz1 = (*env)->FindClass(env, "learn/java/jni/JNICallJavaMethod");
    if (jclz1 == NULL)
    {
        printf("JNI: jclz is NULL.");
        return ji;
    }
    jmethodID jmethodId1 = NULL;
    jmethodId1 = (*env)->GetMethodID(env, jclz1, "<init>"."()V");
    if (jmethodId1 == NULL)
    {
        printf("JNI: jmethodId1 is NULL.");
        return ji;
    }
    jobject jobj1 = (*env)->NewObject(env, jclz1, jmethodId1);
    (*env)->CallVoidMethod(env, jobj1, jmethodId1);

    // Call a non-static method of another class
    jmethodID jmethodId2 = NULL;
    jmethodId2 = (*env)->GetMethodID(env, jclz1, "func"."(I)I");
    if (jmethodId2 == NULL)
    {
        return ji;
    }
    jobject jobj2 = (*env)->NewObject(env, jclz1, jmethodId2);
    jint i1 = (*env)->CallIntMethod(env, jobj2, jmethodId2, 5);
    printf("JNI: func returns %d.\n", i1);

    // Call another class's static method
    jmethodID jmethodId3 = NULL;
    jmethodId3 = (*env)->GetStaticMethodID(env, jclz1, "staticFunc"."([I)I");
    if (jmethodId3 == NULL)
    {
        printf("JNI: jmethodId3 is NULL.");
        return ji;
    }
    jint i2 = (*env)->CallStaticIntMethod(env, jclz1, jmethodId3, jArr);
    printf("JNI: staticFunc returns %d.\n", i2);

    // Call methods in the same class as native methods
    jclass jclz0 = NULL;
    jclz0 = (*env)->GetObjectClass(env, jobj);
    if (jclz0 == NULL)
    {
        printf("JNI: jclz0 is NULL.");
        return ji;
    }
    jmethodID jmethodId4 = NULL;
    jmethodId4 = (*env)->GetMethodID(env, jclz0, "func2"."()Ljava/lang/String;");
    if (jmethodId4 == NULL)
    {
        printf("JNI: jmethodId4 is NULL.");
        return ji;
    }

    // Receives the string returned by the Java method and prints it on the JNI side
    jstring jstr = (jstring)(*env)->CallObjectMethod(env, jobj, jmethodId4);
    char *ptr_jstr = (char *)(*env)->GetStringUTFChars(env, jstr, 0);
    printf("JNI: func2 returns %s\n", ptr_jstr);

    return ji + 100;
}
Copy the code

Obviously, this section of JNI code is the implementation of native method in JNI side, which successively

  1. Creates a jintArray;
  2. Java side JNICallJavaMethod class constructor called;
  3. Non-static methods of the JNICallJavaMethod class;
  4. Static method of JNICallJavaMethod class;
  5. Non-static methods of the JNICallJavaMethodNative class.

The following code is a separate analysis of the above code.

Calling the constructor

Steps:

  1. Load class, the runtime class of the class in which the method is called, i.ejclass;
  2. Gets the method ID, that isjmethodID;
  3. Create an instance of a class, i.ejobject;
  4. Call the method.

Note:

  1. The MethodName parameter is passed directly<init>Can;
  2. Constructors return no value on the Java side, not even void; However, in the method signature on the JNI side, the return value is void. For example, the signature of a class’s default constructor is()V;
  3. JNI method in generalGetXxx()All processes must be nulled.

Code:

    // Call another class's constructor
    jclass jclz1 = NULL;
    jclz1 = (*env)->FindClass(env, "learn/java/jni/JNICallJavaMethod");
    if (jclz1 == NULL)
    {
        printf("JNI: jclz is NULL.");
        return ji;
    }
    jmethodID jmethodId1 = NULL;
    jmethodId1 = (*env)->GetMethodID(env, jclz1, "<init>"."()V");
    if (jmethodId1 == NULL)
    {
        printf("JNI: jmethodId1 is NULL.");
        return ji;
    }
    jobject jobj1 = (*env)->NewObject(env, jclz1, jmethodId1);
    (*env)->CallVoidMethod(env, jobj1, jmethodId1);
Copy the code

Call a non-static method

Steps:

  1. Load the class, the runtime class of the called method’s class, that is, JClass;
  2. Obtain methodID, namely jmethodID;
  3. Create an instance of the class, jobject;
  4. Call the method.

Note:

  1. Because this non-static method belongs to the same class as the constructor above, you can skip step 1 of loading the runtime class and use the jClass you already got.
  2. On the Java side, objects of a class can call multiple methods; But jobject on the JNI side corresponds to jmethodID. So, even if different methods called on the JNI side belong to the same class, different Jobject needs to be created and cannot be shared; As you can see from the JNI function that creates jobject:
// Jobject corresponds to jmethodID:
jobject jobj1 = (*env)->NewObject(env, jclz1, jmethodId1);
Copy the code

Code:

    // Call a non-static method of another class
    jmethodID jmethodId2 = NULL;
    jmethodId2 = (*env)->GetMethodID(env, jclz1, "func"."(I)I");
    if (jmethodId2 == NULL)
    {
        return ji;
    }
    jobject jobj2 = (*env)->NewObject(env, jclz1, jmethodId2);
    jint i1 = (*env)->CallIntMethod(env, jobj2, jmethodId2, 5);
    printf("JNI: func returns %d.\n", i1);
Copy the code

Calling static methods

Steps:

  1. Load the class, the runtime class of the called method’s class, that is, JClass;
  2. Obtain methodID, namely jmethodID;
  3. Call the method.

Note:

  1. Because this non-static method belongs to the same class as the constructor above, you can skip step 1 of loading the runtime class and use the jClass you already got.
  2. Because static method calls do not require an object instance, jobject is not required when Java static methods are called.

Code:

    // Call another class's static method
    jmethodID jmethodId3 = NULL;
    jmethodId3 = (*env)->GetStaticMethodID(env, jclz1, "staticFunc"."([I)I");
    if (jmethodId3 == NULL)
    {
        printf("JNI: jmethodId3 is NULL.");
        return ji;
    }
    jint i2 = (*env)->CallStaticIntMethod(env, jclz1, jmethodId3, jArr);
    printf("JNI: staticFunc returns %d.\n", i2);
Copy the code

Call the method of the native method’s class

The non-static method is taken as an example here, because the Java side method and the Native method are in the same class, and when the JNI side implements the native method, it will pass in a JClass/Jobject, and the corresponding Java side native method declaration is static Native /native. At this point we can use the jclass passed in directly, or use (*env)->GetObjectClass(env,jobj) to get the runtime class. The key is that to call any method, you first need to load the class of the method into the JVM runtime environment.

Code:

    // Call methods in the same class as native methods
    jclass jclz0 = NULL;
    jclz0 = (*env)->GetObjectClass(env, jobj);
    if (jclz0 == NULL)
    {
        printf("JNI: jclz0 is NULL.");
        return ji;
    }
    jmethodID jmethodId4 = NULL;
    jmethodId4 = (*env)->GetMethodID(env, jclz0, "func2"."()Ljava/lang/String;");
    if (jmethodId4 == NULL)
    {
        printf("JNI: jmethodId4 is NULL.");
        return ji;
    }

    // Receives the string returned by the Java method and prints it on the JNI side
    jstring jstr = (jstring)(*env)->CallObjectMethod(env, jobj, jmethodId4);
    char *ptr_jstr = (char *)(*env)->GetStringUTFChars(env, jstr, 0);
    printf("JNI: func2 returns %s\n", ptr_jstr);
Copy the code

Q&A

How to create an integer array in JNI

Steps:

  1. Declare the array name and array length, i.e. JArr, 4;
  2. Get a pointer to the array element type (jint) by calling (*env)->GetIntArrayElements(env, jArr, NULL);
  3. Using Pointers, assign values to elements;
  4. The pointer resource is freed and the array is preserved.

Code:

//todo: create an int array on JNI side
    jintArray jArr = (*env)->NewIntArray(env, 4);/ / step 1
    jint *arr = (*env)->GetIntArrayElements(env, jArr, NULL);/ / step 2
    arr[0] = 0;/ / step 3
    arr[1] = 10;
    arr[2] = 20;
    arr[3] = 30;
    (*env)->ReleaseIntArrayElements(env,jArr,arr,0);/ / step 4
Copy the code
Java side method returns String. How to print the return value when JNI calls?

Steps:

  1. Define the jstring variable and force Jobject with (jstring);
  2. Define a character pointer, strong with (char *);
  3. Printing.
    // Receives the string returned by the Java method and prints it on the JNI side
    jstring jstr = (jstring)(*env)->CallObjectMethod(env, jobj, jmethodId4);
    char *ptr_jstr = (char *)(*env)->GetStringUTFChars(env, jstr, 0);
    printf("JNI: func2 returns %s\n", ptr_jstr);
Copy the code
Print sequence of the JNI and Java consoles

The conclusion is:

Console printing on the JNI side must occur after the Java side program has finished running.

We can debug to see the phenomenon:

B: What do you think?

A: In both calls to constructor and non-static methods, we need to create a jobject that corresponds to jmethodId with (*env)->NewObject(env, JCLZ, jmethodId), so the constructor is called twice.

Func is called twice?

Answer: to be answered!

Can I call java-side methods without the implementation of native methods?

A: Yes, JNI is Java’s cross-platform implementation mechanism for Java to interact with native code. The above process is generally carried out in JNI side native method implementation, because the JNI implementation of native method has a pointer to JNIEnv*, which is the easiest way to obtain JNIEnv*, but not the only way. How do I get JNIEnv*? To solve!

3.3 JNI handles strings passed from Java

Differences between Java and C strings

  • Java uses utF-16 16bit encoding.
  • The UTF-8 unicode encoding used in JNI is 1 byte for English and 3 bytes for Chinese.
  • C/C++ using ASCII encoding, Chinese encoding GB2312 encoding, Chinese 2 bytes.

Actual code

//Java:
package JNICallJava;

public class GetSetJavaString {
    static {
        System.load("E:\\_Projects_\\JNI_Projects\\JNI_C\\cmake-build-debug\\libGetSetJavaStringLib.dll");
    }
    public static native String func(String s);
    public static void main(String[] args) {
        String str = func("--Do you enjoy coding?"); System.out.println(str); }}Copy the code
//C:
#include "stdio.h"
#include "jni.h"
JNIEXPORT jstring JNICALL Java_JNICallJava_GetSetJavaString_func
        (JNIEnv *,jclass,jstring);// There is no special.h file for this declaration.

JNIEXPORT jstring JNICALL Java_JNICallJava_GetSetJavaString_func
        (JNIEnv *env,jclass jclz,jstring jstr){
    const char *chr = NULL;// Character pointer definitions are separated from initialization
    jboolean iscopy;// Determine whether the jString was successfully converted to a char pointer
    chr = (*env)->GetStringUTFChars(env,jstr,&iscopy);// the &iscopy position is usually passed NULL
    if(chr == NULL) {return NULL;// Exception handling
    }
    char buf[128] = {0};// Apply for space + initialize
    sprintf(buf,"%s\n--Yes, I do.",chr);// String concatenation
    (*env)->ReleaseStringUTFChars(env,jstr,chr);// Programming habit, free memory
    return (*env)->NewStringUTF(env,buf);
}
Copy the code
//CMakeLists.txt
add_library(GetSetJavaStringLib SHARED  GetSetJavaString.c)
Copy the code

The results

Exception handling

In the above code example, the GetStringUTFChars() method converts the JString variable of JNI to a CHAR pointer that can be operated on in C. This process may fail. In fact, any conversion process may fail, and the target variable needs to be defined and initialized separately, with null exception handling.

C string concatenation

In C, there are no strings; strings are character Pointers. Its splicing process is not as simple as Java and other languages, divided into the following process:

  1. Malloc requests space
  2. Initialize the
  3. Concatenated string
  4. Free memory

Flexible static registration

  • In this code, instead of using Java Native generated.h files, we write a JNI static register directly before implementing the JNI method, which is also feasible, even the prior declaration of the register is not written. At this point our add_library() value in cmakelists.txt contains the.c file. The core is that add_library() must contain native methods in JNI implementation functions. The.h file is more of a Java command generated to teach you how to write JNI implementation, not important.
  • JNI ignores access control rights on the Java side, but distinguishes between static and non-static.