Jelajahi Sumber

# doc:JNI(Java Native Interface)的参数传递和返回的文档

yang yi 2 minggu lalu
induk
melakukan
bcc30e9207
1 mengubah file dengan 565 tambahan dan 0 penghapusan
  1. 565 0
      doc/JNI(Java Native Interface)的参数传递和返回.md

+ 565 - 0
doc/JNI(Java Native Interface)的参数传递和返回.md

@@ -0,0 +1,565 @@
+# 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 <jni.h>
+ /* 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 <stdio.h>
+ 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 <jni.h>
+ /* 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 使用笔记](https://icejoywoo.github.io/2022/08/22/intro-to-jni.html)
+
+[案例代码仓库](https://git.anyi.space/yangyi/JNI_learn)