JNI/NDK Development Guide (6) - C/C++ Access Java Instance Methods and Static Methods

Through the study of the previous five chapters, we know how to access the basic data types, strings and arrays in the JVM through JNI functions. The next step is to learn how native code interacts with properties and methods of arbitrary objects in the JVM . For example, the native code calls a method or property of an object in the Java layer, which is usually what we call a callback from a native function in the C/C++ layer. This knowledge point is introduced in two articles. This article first introduces method callbacks, and in Chapter 7, native code accesses Java properties.

Before that, let's review the implementation principle in the JVM when calling a method in Java, which is helpful to explain the mechanism of the native code calling Java method implementation. Children's shoes who have written Java know that calling a static method of a class  can be called directly through the class name. method . This is too simple, what is there to say. . . But in this calling process, the JVM does a lot of work for us. When we are running a Java program, the JVM will first load all the relevant class files used by the program into the JVM, and load it in an on-demand manner, that is to say, a class is only used when it is used. It will be loaded when the time, the purpose of this design is also to improve the performance of the program and save memory. So before we call a static method with a class name, the JVM will first determine whether the class has been loaded. If it is not loaded into the JVM by the ClassLoader , the JVM will look for the class from the classpath path. If it is found, it will be loaded into the JVM. In the JVM, then the static method of the class is called. If it is not found, the JVM will throw a java.lang.ClassNotFoundException exception, indicating that the class cannot be found. ClassLoader is a mechanism for JVM to load class bytecode files. If you don't know much about children's shoes, please read the article "In-depth Analysis of Java ClassLoader Principles" . In fact, in JNI development, the local code also accesses the static method or instance method of the class according to the above process. The following is an example to introduce each step in the process of calling the Java method by the local code in detail:

package com.study.jnilearn;

/**
 * AccessMethod.java
 * 本地代码访问类的实例方法和静态方法
 * @author yangxin
 */
public class AccessMethod {
	
	public static native void callJavaStaticMethod(); 
	public static native void callJavaInstaceMethod();
	
	public static void main(String[] args) {
		callJavaStaticMethod();
		callJavaInstaceMethod();
	}
	
	static {
		System.loadLibrary("AccessMethod");
	}
}
package com.study.jnilearn;

/**
 * ClassMethod.java
 * 用于本地代码调用
 * @author yangxin
 */
public class ClassMethod {
	
	private static void callStaticMethod(String str, int i) {
		System.out.format("ClassMethod::callStaticMethod called!-->str=%s," +
				" i=%d\n", str, i);
	}
	
	private void callInstanceMethod(String str, int i) {
		System.out.format("ClassMethod::callInstanceMethod called!-->str=%s, " +
				"i=%d\n", str, i);
	}
}

Header file generated by AccessMethod.class:

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

#ifndef _Included_com_study_jnilearn_AccessMethod
#define _Included_com_study_jnilearn_AccessMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_AccessMethod
 * Method:    callJavaStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
  (JNIEnv *, jclass);

/*
 * Class:     com_study_jnilearn_AccessMethod
 * Method:    callJavaInstaceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

The implementation of the function prototype in the header file by the native code:

// AccessMethod.c

#include "com_study_jnilearn_AccessMethod.h"

/*
 * Class:     com_study_jnilearn_AccessMethod
 * Method:    callJavaStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
(JNIEnv *env, jclass cls)
{
    jclass clazz = NULL;
    jstring str_arg = NULL;
    jmethodID mid_static_method;
    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
    clazz =(*env)->FindClass(env,"com/study/jnilearn/ClassMethod");
    if (clazz == NULL) {
        return;
    }
    
    // 2、从clazz类中查找callStaticMethod方法
    mid_static_method = (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V");
    if (mid_static_method == NULL) {
        printf("找不到callStaticMethod这个静态方法。");
        return;
    }
    
    // 3、调用clazz类的callStaticMethod静态方法
    str_arg = (*env)->NewStringUTF(env,"我是静态方法");
    (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);
    
    // 删除局部引用
    (*env)->DeleteLocalRef(env,clazz);
    (*env)->DeleteLocalRef(env,str_arg);
}

/*
 * Class:     com_study_jnilearn_AccessMethod
 * Method:    callJavaInstaceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
(JNIEnv *env, jclass cls)
{
    jclass clazz = NULL;
    jobject jobj = NULL;
    jmethodID mid_construct = NULL;
    jmethodID mid_instance = NULL;
    jstring str_arg = NULL;
    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
    clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod");
    if (clazz == NULL) {
        printf("找不到'com.study.jnilearn.ClassMethod'这个类");
        return;
    }
    
    // 2、获取类的默认构造方法ID
    mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V");
    if (mid_construct == NULL) {
        printf("找不到默认的构造方法");
        return;
    }
    
    // 3、查找实例方法的ID
    mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");
    if (mid_instance == NULL) {
        
        return;
    }
    
    // 4、创建该类的实例
    jobj = (*env)->NewObject(env,clazz,mid_construct);
    if (jobj == NULL) {
        printf("在com.study.jnilearn.ClassMethod类中找不到callInstanceMethod方法");
        return;
    }
    
    // 5、调用对象的实例方法
    str_arg = (*env)->NewStringUTF(env,"我是实例方法");
    (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);
    
    // 删除局部引用
    (*env)->DeleteLocalRef(env,clazz);
    (*env)->DeleteLocalRef(env,jobj);
    (*env)->DeleteLocalRef(env,str_arg);
}

operation result:

Code analysis:

AccessMethod.java is the entry point of the program. In the main method, two native methods, callJavaStaticMethod and callJavaInstaceMethod, are respectively called to test the callStaticMethod static method and callInstanceMethod instance method in MethodClass.java of the native layer. The return of these two methods The values ​​are all Void, and there are two parameters, String and int

One, callJavaStaticMethod static method implementation description

JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
(JNIEnv *env, jclass cls)

Locate line 31 of AccessMethod.c:

(*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);

The prototype of the CallStaticVoidMethod function is as follows:

void (JNICALL *CallStaticVoidMethod)(JNIEnv *env, jclass cls, jmethodID methodID, ...);

The function accepts 4 parameters:

env: JNI function table pointer

cls: the Class object that calls the static method

methodID: method ID (because there will be multiple methods in a class, a unique identifier is required to determine which method in the class is called)

Parameter 4: List of method arguments

According to the prompt of the function parameters, the callback of the Java static method is completed in the following four steps:

Step 1: Call the FindClass function, pass in a Class descriptor, the JVM will search for the class from the classpath path, and return the jclass type (used to store a reference to the Class object). Note that the Class descriptor of ClassMethod is com/study/jnilearn/ClassMethod, replace all . (dots) with **/** (backslashes)

(*env)->FindClass(env,"com/study/jnilearn/ClassMethod");

**Step 2:** Call the GetStaticMethodID function, get the callStaticMethod method ID from the ClassMethod class, and return the jmethodID type (used to store the method reference). The actual parameter clazz is the jclass object found in the first step, the actual parameter "callStaticMethod" is the method name, and the actual parameter "(Ljava/lang/String;I)V" is the method signature

(*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V");

**Step 3:** Call the CallStaticVoidMethod function and execute the ClassMethod.callStaticMethod method call. str_arg and 100 are the arguments of the callStaticMethod method.

str_arg = (*env)->NewStringUTF(env,"我是静态方法");
(*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);

Note: The JVM defines functions for return values ​​of all data types. The return type of the above callStaticMethod method is Void, so CallStaticVoidMethod is called. According to the different return value types, JNI provides a series of functions with different return values, such as: CallStaticIntMethod, CallStaticFloatMethod, CallStaticShortMethod, CallStaticObjectMethod, etc., which respectively indicate that the return value is int, float, short, Object type functions, and the reference type calls CallStaticObjectMethod uniformly function. In addition, each function that returns a value type provides an implementation that accepts 3 types of arguments: CallStaticXXXMethod (env, clazz, methodID , ...), CallStaticXXXMethodV(env, clazz, methodID, va_list args), CallStaticXXXMethodA( env, clazz, methodID, const jvalue  args), respectively: receiving variable parameter list, receiving va_list as actual parameter and receiving const jvalue as actual parameter. The following are the function prototypes of the three arguments of CallStaticVoidMethod in the jni.h header file:

 void (JNICALL *CallStaticVoidMethod)
      (JNIEnv *env, jclass cls, jmethodID methodID, ...);
    void (JNICALL *CallStaticVoidMethodV)
      (JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
    void (JNICALL *CallStaticVoidMethodA)
      (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args);

The fourth step , release local variables

// 删除局部引用
(*env)->DeleteLocalRef(env,clazz);
(*env)->DeleteLocalRef(env,str_arg);

Although the function ends, the JVM will automatically release the memory space occupied by all local reference variables. But it is safer to release it manually, because a reference table is maintained in the JVM to store local and global reference variables. After testing, in the Android NDK environment, the maximum storage space of this table is 512 references. If it exceeds this number It will cause the reference table to overflow and the JVM will crash. Tested in the PC environment, no matter how many local references are applied and not released, it will not crash. I guess the reason may be different from the implementation of JVM and Android Dalvik virtual machine. Therefore, it is a good habit to release in time when there is an application! (local references and global references will be described in detail in later articles)

Two, callInstanceMethod instance method implementation description

JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
(JNIEnv *env, jclass cls)

Locate line 43 of AccessMethod.c:

(*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);

The prototype of the CallVoidMethod function is as follows:

void (JNICALL *CallVoidMethod) (JNIEnv *env, jobject obj, jmethodID methodID, ...);

The function accepts 4 parameters:

env: JNI function table pointer

obj: the instance on which the method was called

methodID: method ID

Parameter 4: List of arguments to the method

According to the prompt of the function parameters, the callback of the Java static method is completed in the following six steps:

**The first step, ** is the same as calling a static method, first get
the Class object of the class through the FindClass function

**The second step, **Get the constructor ID of the class, because the object that creates the class will first call the constructor of the class. Here is an example of the default constructor

(*env)->GetMethodID(env,clazz, "<init>","()V");

Represents the name of the constructor of the class, ()V represents the constructor with no parameters and no return value (that is, the default constructor)

**The third step, **Call GetMethodID to get the method ID of callInstanceMethod

(*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");

**The fourth step,** call the NewObject function to create an instance object of the class

(*env)->NewObject(env,clazz,mid_construct);

**The fifth step, **Call the CallVoidMethod function, execute the ClassMethod.callInstanceMethod method call, str_arg and 200 are method arguments

str_arg = (*env)->NewStringUTF(env,"我是实例方法");
(*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);

Like JNI calling Java static methods, JVM defines related functions (CallXXXMethod) for the return values ​​of all data types, such as: CallIntMethod, CallFloatMethod, CallObjectMethod, etc., and also provides function implementations that support three types of actual parameters to CallVoidMethod as an example, the following is the prototype of the function in the jni.h header file:

void (JNICALL *CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
void (JNICALL *CallVoidMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
void (JNICALL *CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args);

**Step 6, **Delete local references (remove from reference table)

// 删除局部引用
(*env)->DeleteLocalRef(env,clazz);
(*env)->DeleteLocalRef(env,jobj);
(*env)->DeleteLocalRef(env,str_arg);

3. Method signature

In the above example, whether to call a static method or an instance method, a jmethodID parameter must be passed in. Because there is method overloading in Java (same method name, different parameter list), it is necessary to explicitly tell the JVM which method in the class or instance to call. When calling the GetMethodID function of JNI to obtain a jmethodID, you need to pass in a method name and method signature. The method name is the method name defined in Java. The format of the method signature is : (formal parameter parameter type list) Return value . In the parameter list, the reference type starts with L, followed by the full path name of the class (replace all . with /), and end with a semicolon. Here are some examples:

The mapping relationship between Java basic types and parameter types and return value types in method signatures is as follows:

For example, the JNI method signature corresponding to String fun(int a, float b, boolean c, String d) is: "(IFZLjava/lang/String;)Ljava/lang/String;"

Summarize:

1. Call the static method using the CallStaticXXXMethod/V/A function, where XXX represents the data type of the return value. Such as: CallStaticIntMethod

2. Call the instance method using the CallXXXMethod/V/A function, where XXX represents the returned data type, such as: CallIntMethod

3. Get the ID of an instance method, use the GetMethodID function, and pass in the method name and method signature

4. Get the ID of a static method, use the GetStaticMethodID function, and pass in the method name and method signature

5. Get the constructor ID, use "" for the method name

6. Get a Class instance of a class, use the FindClass function, and pass in the class descriptor. The JVM will start searching from the classpath directory.

7. Create an instance of a class, use the NewObject function, pass in the Class reference and the constructor ID

8. Delete the local variable reference, use DeleteLocalRef, and pass in the reference variable

9. Method signature format: (formal parameter parameter list) return value type. Note: The parameter list does not need to be separated by spaces or other characters

10. Class descriptor format: L package name path/class name;, and the package names are separated by /. Such as: Ljava/lang/String;

11. After calling GetMethodID to get the method ID and calling FindClass to get the Class instance, make an exception judgment

Sample code download address: https://code.csdn.net/xyang81/jnilearn

Guess you like

Origin blog.csdn.net/shangsongwww/article/details/122493215