JNI(Java Native Interface)的参数传递和返回.md 20 KB

JNI(Java Native Interface)的参数传递和返回

Java数据类型与C/C++本地数据类型的映射关系

  • Java的基本数据类型表示映射到C/C++的表示都是在原来的标识字符加上字符"j"

例如:Java的int类型在C/C++中表示为jint类型

  • 对象类型使用jobject表示

  • "void"的表示在Java和C/C++中保持一致

在头文件"jni_md.h"中有如下定义

 typedef long jint;
 typedef __int64 jlong;
 typedef signed char jbyte;

头文件"jni_md.h"在JDk的"include/linux"目录下

在头文件"jni.h"中有如下定义

 typedef unsigned char   jboolean;
 typedef unsigned short  jchar;
 typedef short           jshort;
 typedef float           jfloat;
 typedef double          jdouble;
 typedef jint            jsize;

头文件"jni.h"在JDk的"include"目录下

映射关系如下表

Java类型 本地类型(C/C++类型) JNI中定义的别名 描述
int long jint 有符号的32位表示
long _int64 jlong 有符号的64位表示
byte signed char jbyte 有符号的8位表示
boolean unsigned char jboolean 无符号的8位表示
char unsigned short jchar 无符号16位表示
short short jshort 有符号16位表示
float float jfloat 32位表示
double double jdouble 64位表示
Object _jobject* jobject 对象类型
void void void

Java基本数据类型中除了boolean类型,其他数据类型都可以直接转换为C/C++的基本数据类型

在C/C++中使用常量"JNI_TRUE"表示"true",常量"JNI_FALSE"表示"false"

>  //jint类型可以直接当作int类型使用
>  int a;
>  jint b = a;
>  long c = b;
>  //jboolean使用unsigned char表示
>  //常量"JNI_TRUE"表示"true"
>  //常量"JNI_FALSE"表示"false"
>  jboolean ztrue = JNI_TRUE;
>  jboolean zfasle = JNI_FALSE;
> ```

### Java数据类型的类型签名表示(与Java VM  Type Signatures规范有关)

> JNI中进行对象相关的操作时需要用到Java数据类型的符号表示

Java数据类型与类型签名映射关系表

| Type Signature(类型签名)  |  Java Type(Java类型)  |
| :-----------------------: | :-------------------: |
|             Z             |        boolean        |
|             B             |         byte          |
|             C             |         char          |
|             S             |         short         |
|             I             |          int          |
|             J             |         long          |
|             F             |         float         |
|             D             |        double         |
| L fully-qualified-class ; | fully-qualified-class |
|          [ type           |        type[]         |
|  ( arg-types ) ret-type   |     method type`      |
|             V             |         void          |

#### 类型签名案例

- `int`类型的类型签名为:`I`
- `boolean`类型的类型签名为:`D`
- `java.lang.String`类型的类型签名为:`Ljava/lang/String`

> "L"表示为对象类型,"Ljava/lang/String"表示具体的对象类型为"java.lang.String"

- `int[]`类型的类型签名为:`[I`

> 一个左中括号"["表示为一维数组,完整"[int"表示为int类型的一维数组

- `int[][]`类型的类型签名为:`[[I`

> 两个左中括号"[["表示为二维数组,完整"[[nt"表示为int类型的二维数组
>
> 总结:有多少个左中括号就表示是多少维数组

- `java.lang.String[]`类型的类型签名为:`[Ljava/lang/String`

- 例如,有如下方法`public static void main(String[] agrs);`对应的方法类型签名为:`([Ljava/lang/String;)V`

> - "()":一对小括号表示方法参数列表
> - "[":一个左中括号表示一维数组
> - "L":表示对象类型
> - "java/lang/String":表示类"java.lang.String"
> - "Ljava/lang/String;":表示"java.lang.String"这个对象类型
> - "V":在这里表示返回值,返回类型为void
>
> 小括号外的类型签名表示方法的返回值类型
>
> 组合在一起的完整含义为,接收一个参数为字符串数组,返回值为void类型的方法

- 再例如`int sum(int a,int b);`对应的类型签名为`(II)I`

> 含义为有两个参数类型为int,返回值类型为int的方法

- `String method(String str1,String str2);`对应的类型签名为`(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String`

> 参数列表有多个对象类型的参数时,对象类型的类型签名结尾需要使用分号分割

### 案例:基本类型的参数传递和返回

- Java代码

package space.anyi.jni.parameter;

public class Main {

 static {
     System.load("/home/yangyi/JNI-learn/c/parameter/JNI_parameter.so");
 }
 public static void main(String[] args) {
     BaseTypeTest baseTypeTest = new BaseTypeTest();
     int sum = baseTypeTest.sum(1, 2);
     System.out.println(sum);
     boolean flag = baseTypeTest.booleanTypeTest(false);
     System.out.println(flag);
 }

} class BaseTypeTest {

 //定义一个有参数和返回值的native方法
 native int sum(int val1,int val2);
 //boolean类型的测试
 native boolean booleanTypeTest(boolean flag);

}


- 生成的头文件内容

生成头文件的命令:`javac -h c/parameter src/main/java/space/anyi/jni/parameter/Main.java`

/* DO NOT EDIT THIS FILE - it is machine generated / #include / Header for class space_anyi_jni_parameter_BaseTypeTest */

#ifndef _Included_space_anyi_jni_parameter_BaseTypeTest #define _Included_space_anyi_jni_parameter_BaseTypeTest #ifdef __cplusplus extern "C" { #endif /*

  • Class: space_anyi_jni_parameter_BaseTypeTest
  • Method: sum
  • Signature: (II)I */ JNIEXPORT jint JNICALL Java_space_anyi_jni_parameter_BaseTypeTest_sum (JNIEnv *, jobject, jint, jint);

/*

  • Class: space_anyi_jni_parameter_BaseTypeTest
  • Method: booleanTypeTest
  • Signature: (Z)Z */ JNIEXPORT jboolean JNICALL Java_space_anyi_jni_parameter_BaseTypeTest_booleanTypeTest (JNIEnv *, jobject, jboolean);

#ifdef __cplusplus } #endif #endif




- 本地方法的实现

#include "space_anyi_jni_parameter_BaseTypeTest.h" #include int sum(int a,int b){

 return a+b;

} JNIEXPORT jint JNICALL Java_space_anyi_jni_parameter_BaseTypeTest_sum(JNIEnv *env, jobject object, jint val1, jint val2){

 //jint类型直接当作int类型使用
 int temp = sum(val1,val2);
 jint result = temp;
 return result;

} JNIEXPORT jboolean JNICALL Java_space_anyi_jni_parameter_BaseTypeTest_booleanTypeTest(JNIEnv *env, jobject object, jboolean flag){

 //jboolean类型当作unsigned char类型使用
 unsigned char val = flag;
 printf("%d\n",val);
 //在C/C++中使用常量"JNI_TRUE"表示"true",常量"JNI_FALSE"表示"false"
 return val ? JNI_FALSE : JNI_TRUE;

}




- 编译为动态链接库

中间编译

gcc -c -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -o c/parameter/JNI_parameter.o c/parameter/JNI_parameter.c


动态链接库编译

gcc -shared -fPIC -o c/parameter/JNI_parameter.so c/parameter/JNI_parameter.o -lc


- 运行测试:`java -cp src/main/java space.anyi.jni.parameter.Main`

输出结果

yangyi@debain12:~/JNI-learn$ java -cp src/main/java space.anyi.jni.parameter.Main 3 0 true




### 案例:引用类型(对象类型)的参数传递和返回

JNI中关于对象的操作与Java的类反射机制非常相似

JNI中对象抽象与Java类反射机制抽象的映射表

| Java 类型反射抽象          | JNI 类型/句柄             | 说明                                                         |
| -------------------------- | ------------------------- | ------------------------------------------------------------ |
| `java.lang.Class`          | `jclass`                  | 表示 Java 类对象,可通过 `FindClass`、`GetObjectClass` 等获取 |
| `java.lang.reflect.Method` | `jmethodID`               | 表示 Java 方法,可通过 `GetMethodID`、`GetStaticMethodID` 获取 |
| `java.lang.reflect.Field`  | `jfieldID`                | 表示 Java 字段,可通过 `GetFieldID`、`GetStaticFieldID` 获取 |
| `java.lang.Object`         | `jobject`                 | 表示 Java 对象实例的通用引用                                 |
| `java.lang.String`         | `jstring`                 | 表示 Java 字符串对象,是 `jobject` 的子类型                  |
| `java.lang.Throwable`      | `jthrowable`              | 表示 Java 异常对象,是 `jobject` 的子类型                    |
| 数组类型(如 `Object[]`)  | `jarray` / `jobjectArray` | 通用数组句柄,具体类型如 `jintArray`、`jobjectArray` 等      |

- Java代码,通过本地方法获取一个对象(对象类型的返回),通过本地方法输出一个对象的信息(对象类型的入参)

package space.anyi.jni.parameter;

import java.util.Arrays;

public class Main {

 static {
     System.load("/home/yangyi/JNI-learn/c/parameter/JNI_object_parameter.so");
 }
 public static void main(String[] args) {

     //JNI对象数据类型的传递
     ObjectTest objectTest = new ObjectTest();
     Student student = objectTest.getStudent();
     System.out.println(student);
     objectTest.printStudentInfo(student);
     System.out.println(student);

// objectTest.free(student);

 }

} class ObjectTest{

 /**
  * 通过JNI获取对象,返回的是全局的JNI引用,需要手动管理,有内存泄露的风险
  * @return
  */
 native Student getStudent();
 native void printStudentInfo(Student student);

 /**
  * 释放JNI创建的全局引用
  * @param student
  */
 native void free(Student student);

} class Student{

 private String name;
 private int age;
 private float height;
 private int[] source;
 private String[] hobby;

 public Student(String name) {
     this.name = name;
 }

 public int getAge() {
     return age;
 }

 public void setAge(int age) {
     this.age = age;
 }

 public float getHeight() {
     return height;
 }

 public void setHeight(float height) {
     this.height = height;
 }

 public int[] getSource() {
     return source;
 }

 public void setSource(int[] source) {
     this.source = source;
 }

 public String[] getHobby() {
     return hobby;
 }

 public void setHobby(String[] hobby) {
     this.hobby = hobby;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public void info(){
     System.out.println("My name is %s,i am %d year old,height:%f".formatted(this.name,this.age,this.height));
 }

 @Override
 public String toString() {
     return "Student{" +
             "name='" + name + '\'' +
             ", age=" + age +
             ", height=" + height +
             ", source=" + Arrays.toString(source) +
             ", hobby=" + Arrays.toString(hobby) +
             '}';
 }

}


- 编译头文件:`javac -h c/parameter/ src/main/java/space/anyi/jni/parameter/Main.java`

得到文件"space_anyi_jni_parameter_ObjectTest.h"

/* DO NOT EDIT THIS FILE - it is machine generated / #include / Header for class space_anyi_jni_parameter_ObjectTest */

#ifndef _Included_space_anyi_jni_parameter_ObjectTest #define _Included_space_anyi_jni_parameter_ObjectTest #ifdef __cplusplus extern "C" { #endif /*

  • Class: space_anyi_jni_parameter_ObjectTest
  • Method: getStudent
  • Signature: ()Lspace/anyi/jni/parameter/Student; */ JNIEXPORT jobject JNICALL Java_space_anyi_jni_parameter_ObjectTest_getStudent (JNIEnv *, jobject);

/*

  • Class: space_anyi_jni_parameter_ObjectTest
  • Method: printStudentInfo
  • Signature: (Lspace/anyi/jni/parameter/Student;)V */ JNIEXPORT void JNICALL Java_space_anyi_jni_parameter_ObjectTest_printStudentInfo (JNIEnv *, jobject, jobject);

/*

  • Class: space_anyi_jni_parameter_ObjectTest
  • Method: free
  • Signature: (Lspace/anyi/jni/parameter/Student;)V */ JNIEXPORT void JNICALL Java_space_anyi_jni_parameter_ObjectTest_free (JNIEnv *, jobject, jobject);

#ifdef __cplusplus } #endif #endif




- 实现本地方法

> C语言与C++语言使用env指针的API不一样,C语言比C++语言版本的API多一对小括号和多传递一个参数
>
> ```
>  //C语言
>  (*env)->functionName(env,args...)
>  //C++语言
>  env->functionName(args...)
> ```

native方法中创建的对象,如果需要在JVM中使用,需要创建全局引用,并将全局引用返回给JVM

> 通过NewGlobalRefh函数创建全局引用
>
> ```
>  //创建JNI全局引用,以便JVM可以使用JNI中创建的对象
>  jobject result = (*env)->NewGlobalRef(env,student);
>  return result;
> ```
>
> 通过DeleteGlobalRef函数释放全局引用
>
> ```
>  (*env)->DeleteGlobalRef(env,ref);
> ```

#include "space_anyi_jni_parameter_ObjectTest.h" JNIEXPORT jobject JNICALL Java_space_anyi_jni_parameter_ObjectTest_getStudent(JNIEnv *env, jobject this){

 printf("start:getStudent\n");
 //C语言与C++语言使用env指针的方式不一样,C语言比C++语言版本的api多一对小括号和多传递一个参数
 //C语言:(*env)->functionName(env,args...)
 //C++语言:env->functionName(args...)
 //1.通过JNIEnv指针获取指定类的jclass,通过类的类型签名指定
 jclass class_Student = (*env)->FindClass(env,"Lspace/anyi/jni/parameter/Student;");
 //2.获取类的构造方法,第一个参数是类的jclass,第二个参数是方法名,第三个参数是方法的类型签名
 //构造方法的名称为:<init>,构造方法的返回值为void
 jmethodID method_constructor_Student = (*env)->GetMethodID(env,class_Student,"<init>","(Ljava/lang/String;)V");
 //3.调用构造方法创建对象,第一个参数是类的jclass,第二参数为jmethodID,从第三个参数开始为方法的入参
 jstring nameStr = (*env)->NewStringUTF(env, "yangyi");
 jobject student = (*env)->NewObject(env,class_Student,method_constructor_Student,nameStr);
 //4.获取类的普通方法,第一个参数是jclass,第二个参数是方法的名称,第三个参数方法的类型签名
 jmethodID student_method_setAge = (*env)->GetMethodID(env,class_Student,"setAge","(I)V");
 //5.调用类的普通方法,第一个参数是将要调用该方法实例对象的jobject,第二个参数是方法对应的jmethodID,从第三个参数开始为方法的入参
 (*env)->CallObjectMethod(env,student,student_method_setAge,18);
 //6.获取类的普通字段,第一个参数是类的jcalss,第二个参数是字段的名称,第三个参数是字段的类型签名
 jfieldID student_field_height = (*env)->GetFieldID(env,class_Student,"height","F");
 //7.给类的普通字段设置值,第一个参数是类实例对应的jobject,第二个参数是字段对应的jfieldID,第三个参数是要设置的目标值
 (*env)->SetFloatField(env,student,student_field_height,1.66);
 //8.释放局部引用资源
 (*env)->DeleteLocalRef(env, class_Student);
 //创建JNI全局引用,以便JVM可以使用JNI中创建的对象
 jobject result = (*env)->NewGlobalRef(env,student);
 printf("end:getStudent\n");
 return result;

}

JNIEXPORT void JNICALL Java_space_anyi_jni_parameter_ObjectTest_printStudentInfo(JNIEnv *env, jobject this, jobject student){

 printf("start:printStudentInfo\n");
 //1.调用对象的普通方法
 //1.1获取jclass
 jclass class_Student = (*env)->GetObjectClass(env,student);
 //1.2获取jmethodID
 jmethodID student_method_info = (*env)->GetMethodID(env,class_Student,"info","()V");
 //1.3call
 (*env)->CallObjectMethod(env,student,student_method_info);
 //2.获取对象的字段值
 //2.1获取jfieldID
 jfieldID student_field_height = (*env)->GetFieldID(env,class_Student,"height","F");
 //2.2获取对象字段对应的值
 jfloat height = (*env)->GetFloatField(env,student,student_field_height);
 printf("height:%f\n",height);
 //基本类型的数组操作
 //3.1创建数组
 jint size = 3;
 jintArray newArray = (*env)->NewIntArray(env, size);
 //3.2设置数组中的值
 //第一个参数为数组的jarray,第二个参数为数组的默认值
 jint *source = (*env)->GetIntArrayElements(env, newArray, NULL);
 source[0] = 90;
 source[1] = 91;
 source[2] = 92;
 //3.3获取数组中的值
 jint val1 = source[0];
 jint val2 = source[1];
 jint val3 = source[2];
 //更新数组的修改,第一个参数为数组的jarray,第二个参数为C/C++中数组,第三个参数为模式(0:将数组拷贝到jarray并释放C/C++中数组的内存;1:拷贝数组但不释放内存;2:不拷贝但释放内存)
 (*env)->ReleaseIntArrayElements(env, newArray, source,0);
 //获取数组的长度
 jsize length = (*env)->GetArrayLength(env, newArray);
 jfieldID student_field_source = (*env)->GetFieldID(env,class_Student,"source","[I");
 (*env)->SetObjectField(env,student,student_field_source,newArray);
 //对象类型的数组操作
 jclass class_String = (*env)->FindClass(env,"Ljava/lang/String;");
 //第一个参数为数组的大小,第二个参数为对象的jclass,第三个参数为数组默认值
 jobjectArray objectArray = (*env)->NewObjectArray(env, size,class_String,NULL);
 //对象数组赋值
 jint index = 0;
 jstring hobby = (*env)->NewStringUTF(env,"学习");
 //第一个参数为jarray,第二个参数为索引,第三个参数为值的jobject
 (*env)->SetObjectArrayElement(env,objectArray,index,hobby);
 //对象数组取值,第一个参数为jarray,第二个参数为索引
 jobject value = (*env)->GetObjectArrayElement(env,objectArray,index);
 jfieldID student_field_hobby = (*env)->GetFieldID(env,class_Student,"hobby","[Ljava/lang/String;");
 (*env)->SetObjectField(env,student,student_field_hobby,objectArray);
 //释放局部引用资源,可选
 (*env)->DeleteLocalRef(env, class_Student);
 printf("end:printStudentInfo\n");

}

JNIEXPORT void JNICALL Java_space_anyi_jni_parameter_ObjectTest_free(JNIEnv *env, jobject this, jobject student){

 printf("start:free\n");
 if(student != NULL){
     printf("释放全局引用\n");
     (*env)->DeleteGlobalRef(env,student);
 }
 printf("end:free\n");

}




- 编译动态链接库

gcc -c -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -o c/parameter/JNI_object_parameter.o c/parameter/JNI_object_parameter.c gcc -shared -fPIC c/parameter/JNI_object_parameter.so c/parameter/JNI_object_parameter.o -lc


> 得到动态链接库"JNI_object_parameter.so"

- 测试输出:`java -cp src/main/java/ space.anyi.jni.parameter.Main`

yangyi@debain12:~/JNI-learn$ java -cp src/main/java/ space.anyi.jni.parameter.Main start:getStudent end:getStudent Student{name='yangyi', age=18, height=1.66, source=null, hobby=null} start:printStudentInfo My name is yangyi,i am 18 year old,height:1.660000 height:1.660000 end:printStudentInfo Student{name='yangyi', age=18, height=1.66, source=[90, 91, 92], hobby=[学习, null, null]} ```

参考资料

JNI 使用笔记

案例代码仓库