介绍
JNI(Java Native Interface)是 Java 提供的一个接口,用于在 Java 程序中调用非 Java 代码,特别是 C 或 C++ 代码。JNI 使得 Java 程序能够直接与本地代码进行交互,从而提高性能,访问底层操作系统特性,或者利用现有的 C/C++ 库。
- 调用本地方法:通过 JNI,Java 代码可以调用用 C/C++ 编写的本地方法。
- 有些性能要求高的操作,Java 本身可能无法满足,这时可以将这些操作用本地代码实现,并通过 JNI 调用,从而提升性能。
简单案例
java调用c/c++时,有一下几个步骤需要经历
- 静态代码块定义需要导入的c++动态链接库(用来连接c++方法的,windows后缀是dll,linux是so)
- 定义native方法(静态的本地方法)
- 编译.h头文件
- c++导入.h头文件,重写java写的本地静态方法
- 编译c++文件为动态链接库
- java代码通过动态链接库运行
public class Study1 {
static {
// 加载动态链接库(windows是.dll,linux是so)
System.loadLibrary("Study1");
}
// native方法(静态的本地方法),需要调用的c函数
public static native int add(int a,int b);
public static void main(String[] args) {
System.out.println(add(1,2));
}
}
编译java文件,获取对应的.class编译文件和.h头文件
# 执行编译.java文件,获取.h头文件(旧版本java用的是javah,而不是java -h)
javac -h .\src\ .java文件
# 如果.java文件编码不是GBK,还需要指定编码格式
javac -h .\src\ -encoding UTF-8 .\src\Study1.java
重写java的native方法(静态的本地方法),直接重写.h的方法
// 重写java的native方法(静态的本地方法)
JNIEXPORT jint JNICALL Java_Study1_add(JNIEnv *,jclass,jint a,jint b){
printf("c++代码被调用\n");
return a+b;
}
编译c++文件,生成动态链接库,得到.dll/.so文件,执行java代码测试结果
g++ -o 输出的.dll文件 -fPIC -shared -I"安装的jdk路径\include\win32" -I"安装的jdk路径\include" 编译的.cpp文件
g++ -o study1.dll -fPIC -shared -I"F:\JAVA\JDK\JDK1.8\include\win32" -I"F:\JAVA\JDK\JDK1.8\include" .\src\Study1.cpp
JNI数据类型
在 JNI(Java Native Interface)中,Java 和 Native 代码(通常是 C 或 C++)之间通过特定的数据格式进行交互。以下是 JNI 中常见的数据格式及其对应关系:
1. 基本数据类型
Java 类型 | JNI 类型 | C/C++ 类型 |
---|---|---|
boolean | jboolean | unsigned char (1字节) |
byte | jbyte | signed char (1字节) |
char | jchar | unsigned short (2字节) |
short | jshort | short (2字节) |
int | jint | int (4字节) |
long | jlong | long long (8字节) |
float | jfloat | float (4字节) |
double | jdouble | double (8字节) |
void | void | void |
2. 引用类型
Java 类型 | JNI 类型 | 说明 |
---|---|---|
String | jstring | Java 字符串 |
Object | jobject | 任意 Java 对象 |
Class | jclass | Java 类 |
Throwable | jthrowable | Java 异常对象 |
3. 数组类型
Java 数组类型 | JNI 类型 | C/C++ 类型 |
---|---|---|
boolean[] | jbooleanArray | jboolean* |
byte[] | jbyteArray | jbyte* |
char[] | jcharArray | jchar* |
short[] | jshortArray | jshort* |
int[] | jintArray | jint* |
long[] | jlongArray | jlong* |
float[] | jfloatArray | jfloat* |
double[] | jdoubleArray | jdouble* |
Object[] | jobjectArray | jobject* |
4. JNI 中的特殊类型
JNI 类型 | 说明 |
---|---|
jmethodID | 方法 ID |
jfieldID | 字段 ID |
JNIEnv* | JNI 环境指针 |
JavaVM* | Java 虚拟机指针 |
5. 常见数据操作
获取数组元素
jintArray intArray = ...;
jint* elements = env->GetIntArrayElements(intArray, NULL);
// 操作数组元素
env->ReleaseIntArrayElements(intArray, elements, 0);
字符串
jstring str = 指针;//jstring是用来存放java的string内容的指针对象
1.解析字符串
jboolean isCopy; //是否复制一个新的指针,str本身就是一个指针对象
const char* fromJava = env->GetStringUTFChars(jStr, &isCopy); //解析为字符串指针
printf("Java String: %s\n", fromJava); // 打印字符串,printf会解析指针,不必使用&变量来解析指针
2.释放指针
env->ReleaseStringUTFChars(str, fromJava); // 释放内存
数组
1.获取数组
// 获取数组指针
jint* a = env->GetIntArrayElements(arr1,isCopy);
// 获取数组指针(按范围)
jint* b = new jint[3];
env->GetIntArrayRegion(arr2,0,env->GetArrayLength(arr2),b);
// 获取数组长度
env->GetArrayLength(arr1);
2.释放数组
env->ReleaseIntArrayElements(intArray, jint, 0);
引用类型(自定义类)
1.获取引用类型
//1.通过jni参数传递类
JNIEXPORT void JNICALL Java_Study_XX(JNIEnv *env, jclass this_){
// 获取当前类
jclass clas = env->GetObjectClass(this_);
}
//2.通过jobject获取类(jobject一般也是通过参数传递)
JNIEXPORT void JNICALL Java_Study_XX(JNIEnv *env, jclass this_,jobject obj){
// 获取当前类
jclass clas = env->GetObjectClass(obj);
}
//3.通过路径获取类
jclass clas = env->FindClass("com/demo/Other");
2.静态方法和属性
// 调用静态方法
// 1.先获取静态方法Id
// 格式:GetStaticMethodID(jclass, "静态方法名", 方法签名)
jmethodID staticMethod = env->GetStaticMethodID(clas, "getinfo", "(Lcom/demo/User;)Ljava/lang/String;");
// 2.调用方法,CallStaticXXMethod,XX为返回类型,一般用CallStaticObjectMethod
jobject staticRes = env->CallStaticObjectMethod(jclass, staticMethod, user);
// 或者jstring staticRes = (jstring)env->CallStaticObjectMethod(jclass, staticMethod, user);
// 获取静态属性
// 1. 获取静态字段ID,使用 GetStaticFieldID
// 格式:GetStaticFieldID(jclass, "属性名", 属性签名)
jfieldID staticField = env->GetStaticFieldID(clas, "staticFieldName", "Ljava/lang/String;");
// 2. 获取静态属性值,使用 GetStaticObjectField(对于对象类型)或 GetStatic<Type>Field(对于基本数据类型)
jstring staticFieldValue = (jstring)env->GetStaticObjectField(clas, staticField);
// 设置静态属性
// 1. 获取静态字段ID,使用 GetStaticFieldID
jfieldID staticField = env->GetStaticFieldID(clas, "staticFieldName", "Ljava/lang/String;");
// 2. 设置静态属性值,使用 SetStaticObjectField(对于对象类型)或 SetStatic<Type>Field(对于基本数据类型)
env->SetStaticObjectField(clas, staticField, newStaticValue);
3.非静态方法和属性
// 调用非静态方法
// 1. 获取非静态方法的ID,使用 GetMethodID
// 格式:GetMethodID(jclass, "方法名", 方法签名)
jmethodID method = env->GetMethodID(clas, "getInfo", "()Ljava/lang/String;");、
// 2. 调用非静态方法,使用 CallObjectMethod
// 这里假设返回值是 String 类型,所以使用 CallObjectMethod 并强制转换为 jstring
jstring res = (jstring)env->CallObjectMethod(user, method);
// 设置非静态属性值
// 1. 获取非静态字段ID,使用 GetFieldID
jfieldID field = env->GetFieldID(clas, "name", "Ljava/lang/String;");
// 2. 设置非静态属性值,使用 SetObjectField(对于对象类型)或 Set<Type>Field(对于基本数据类型)
env->SetObjectField(user, field, newFieldValue);
4.签名规则
(<参数签名>)<返回类型签名>
- 基本类型:
- Z:boolean
- B:byte
- C:char
- D:double
- F:float
- I:int
- J:long
- S:short
- V:void
- 对象类型(一定要加";"符号)
- L<包名>/<类名>;:表示对象类型,例如 Lcom/demo/User; 表示 com.demo.User 类型的对象。
- [:表示数组。例如,[I 表示一个 int 类型的数组,[Ljava/lang/String; 表示一个 String 类型的数组。
//案例
jmethodID staticMethod = env->GetStaticMethodID(userClass, "getStaticInfo", "(Lcom/demo/User;)Ljava/lang/String;");