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类型的类型签名为:Iboolean类型的类型签名为:Djava.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]}