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中定义的别名描述
intlongjint有符号的32位表示
long_int64jlong有符号的64位表示
bytesigned charjbyte有符号的8位表示
booleanunsigned charjboolean无符号的8位表示
charunsigned shortjchar无符号16位表示
shortshortjshort有符号16位表示
floatfloatjfloat32位表示
doubledoublejdouble64位表示
Object_jobject*jobject对象类型
voidvoidvoid

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类型)
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[ typetype[]
( arg-types ) ret-typemethod type`
Vvoid

类型签名案例

  • 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.Classjclass表示 Java 类对象,可通过 FindClassGetObjectClass 等获取
java.lang.reflect.MethodjmethodID表示 Java 方法,可通过 GetMethodIDGetStaticMethodID 获取
java.lang.reflect.FieldjfieldID表示 Java 字段,可通过 GetFieldIDGetStaticFieldID 获取
java.lang.Objectjobject表示 Java 对象实例的通用引用
java.lang.Stringjstring表示 Java 字符串对象,是 jobject 的子类型
java.lang.Throwablejthrowable表示 Java 异常对象,是 jobject 的子类型
数组类型(如 Object[]jarray / jobjectArray通用数组句柄,具体类型如 jintArrayjobjectArray
  • 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 使用笔记

案例代码仓库