JNI的概念
- 定义
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植 -
原理
- 开发工具
1、vs2015
2、eclipse(或者Android studio)
3、java环境
JNI的调用过程
步骤:
1.编写带有native声明的方法的java类
2.编译生成class文件
3.利用javah生成(.h)的头文件 命令:javah 类名, 注:不需要class后缀
4.将(.h)头文件复制到vs下,创建(.cpp)或者(.c)文件实现(.h)头文件声明的方法
5.实现完成后,编译成dll库
6.将dll复制到java项目的根目录,调用System.loadLibrary("dll库名"); //注:不要dll后缀
7.在代码里面调用native方法,访问native(.cpp 或者 .c)的代码栗子:
1、java上的native定义
//定义
public native static String getStringFromCPP();
//调用
public static void main(String[] args) {
System.out.println(getStringFromCPP());
}
2、利用javac或者编译器直接编译,生成class文件
3、利用jdk下的javah 生成(.h)的头文件
4、将生成的头文件放置到vs新建的项目,如下; 还需要将JDK目录下的include 目录下的jni.h 和 jni_md.h文件copy到项目
因为生成的JniMain.h文件需要依赖到这两个文件,同时将JniMain.h中的 #include
改成 #include "jni.h"
5、创建C++或者C文件实现JniMain的方法, 这边创建JniDemo.cpp
, 引入头文件
具体代码:
#include "stdafx.h"
#include "JniMain.h"
#include
/*
* Class: JniMain
* Method: getStringFromCPP
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromCPP
(JNIEnv *env, jclass jclaz) {
return env->NewStringUTF("java static method call C++ back string");
}
6、打包成dll
这边vs项目创建的是win32的项目,所以需要配置成dll
在项目右键 ->属性
由于个人的环境是64位的,所以配置管理,需要修改为x64位
生成dll
7、将dll 复制到java项目工程的根目录,并加载dll库, 运营程序
public class JniMain {
//静态方法
public native static String getStringFromCPP();
static{
System.loadLibrary("Jni");
}
public static void main(String[] args) {
System.out.println(getStringFromCPP());
}
}
结果:
调用的分析
JNI的数据类型
- JNI基本数据类型:
java | C/C++ |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
- 引用类型:
java | C/C++ |
---|---|
String | jstring |
Object | jobject |
基本数据类型数组:
//type[] jTypeArray;
byte[] jByteArray;引用类型数组
Object jobjectArray;
JNI对应的java属性与方法签名
在jni调用中,返回值和参数,以及静态字段和实例字段,有对应着相应的签名,如下表格:
这些签名的时候在接下的实例讲解中会用到;
简而言之,在jni中涉及到类型的使用(包括基本类和引用类型)
- 方法签名例子:
方法1:
public string addTail(String tail, int index)
其对应的签名如下:
(Ljava/util/String;I)Ljava/util/String;
方法2:
public int addValue(int index, String value,int[] arr)
其对应的签名如下:
(ILjava/util/String;[I)I
-
javap命令查看class文件中对应jni的签名
命令:javap -s -p class文件的路径
native修饰的静态方法
- java代码:
public native static String getStringFromCPP();
- C++ 代码:
/*
* Class: JniMain
* Method: getStringFromCPP
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromCPP
(JNIEnv *env, jclass jclaz) {
return env->NewStringUTF("java static method call C++ back string");
}
- 说明:
java中定义native方法是静态的话,生成的接口方法的参数就是(JNIEnv *env, jclass jclaz)
JNIEnv
: 是jni接口调用的api指针
jclass
: 表示的就是native修饰的java静态方法所在的类
native修饰的非静态方法
- java代码:
public native String getStringFromCPP2();
- C++代码:
/*
* Class: JniMain
* Method: getStringFromCPP2
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromCPP2
(JNIEnv *env, jobject job) {
return env->NewStringUTF("java non-static method call C++ back string ");
}
- 说明:
java中定义native方法是非静态的话,生成的接口方法的参数是(JNIEnv *env, jobject job)
JNIEnv
: 是jni接口调用的api指针
jobject
: 表示的就是native修饰的java非静态方法所在类的对象
访问java类中的成员变量
- java代码:
public String key = "key";
public native void accessField(); //该native方法用于调用c++的接口访问java变量
- C++代码:
/*
* Class: JniMain
* Method: accessField
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniMain_accessField
(JNIEnv *env, jobject job) {
//先获取对应的java类
jclass jclaz = env->GetObjectClass(job);
//第二个参数对应的是java的变量名,第三个是类型的签名
jfieldID fid = env->GetFieldID(jclaz, "key", "Ljava/lang/String;");
char text[100] = "Jni change string field value ";
if (fid == NULL) { // 如果字段为 NULL ,直接退出,查找失败
return;
}
// 获取字段对应的值
jstring jstr = (jstring)env->GetObjectField(job, fid);
const char * str = env->GetStringUTFChars(jstr, NULL);
strcat(text, str);
jstring key = env->NewStringUTF(text);
//修改key的值
env->SetObjectField(job, fid, key);
}
- 说明:类似java的反射,步骤如下:
1、获取 Java 对象的类
2、获取对应字段的 id
3、获取具体的字段值
访问java类中的静态变量
- java代码:
public static int count = 9;
public native void accessStaticField(); //该native方法用于调用c++的接口访问java静态变量
- C++代码:
/*
* Class: JniMain
* Method: accessStaticField
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniMain_accessStaticField
(JNIEnv *env, jobject job) {
//获取类名
jclass jclaz = env->GetObjectClass(job);
//获取静态字段 第一个参数是类,第二个参数对应的是java的变量名,第三个是类型的签名
jfieldID fid = env->GetStaticFieldID(jclaz, "count", "I");
if (fid == NULL) {
return;
}
// 获取字段对应的值
jint count = env->GetStaticIntField(jclaz, fid);
count = 20;
// 修改字段的值
env->SetStaticIntField(jclaz, fid, count);
}
- 说明:静态字段的访问类似实例字段的访问,步骤相同
jni中调用java某个对象的方法
- java代码:
public class Animal {
protected String name;
public static int num = 0;
public Animal(String name) {
this.name = name;
}
//jni访问的非静态方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getNum() {
return num;
}
public static String getUID(String id) {
return "10001"+id;
}
}
//另一个类的native方法
public native void callInstanceMethod(Animal animal);
- C++代码:
/*
* Class: JniMain
* Method: callInstanceMethod
* Signature: (LAnimal;)V
*/
JNIEXPORT void JNICALL Java_JniMain_callInstanceMethod
(JNIEnv *env, jobject instance, jobject animal) {
// 获得具体的类
jclass cls = env->GetObjectClass(animal);
// 获得具体的方法 id
jmethodID mid = env->GetMethodID(cls, "setName", "(Ljava/lang/String;)V");
if (mid == NULL) {
return;
}
//设置要穿的参数
jstring name = env->NewStringUTF("baozi");
//调用java的方法
env->CallVoidMethod(animal, mid, name);
}
- 说明:
与访问字段不同的是,GetFieldID 方法换成了 GetMethodID 方法,另外由 CallVoidMethod 函数来调用具体的方法,前面两个参数是获得的类和方法 id,最后的参数是具体调用方法的参数(签名)。方法签名 = (参数类型额签名) + 返回值类型的签名
GetMethodID 方法的第一个参数就是具体的 Java 类型,第二个参数是该 Java 类的对应实例方法的名称,第三个参数就是该方法对应的返回类型和参数签名转换成 Native 对应的描述。
对于不需要返回值的函数,调用 CallVoidMethod 即可,对于返回值为引用类型的,调用 CallObjectMethod 方法,对于返回基础类型的方法,则有各自对应的方法调用,比如:CallBooleanMethod、CallShortMethod、CallDoubleMethod 等等
jni中调用java类的某个静态方法
- java代码:
public class Animal {
protected String name;
public static int num = 0;
public Animal(String name) {
this.name = name;
}
//jni访问的非静态方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getNum() {
return num;
}
//jni访问的静态方法
public static String getUID(String id) {
return "10001"+id;
}
}
//另一个类的native方法
public native String callStaticMethod(Animal animal);
- C++代码:
/*
* Class: JniMain
* Method: callStaticMethod
* Signature: (LAnimal;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JniMain_callStaticMethod
(JNIEnv *env, jobject instance, jobject animal) {
//获取具体的类
jclass jclz = env->FindClass("Animal");//参数也累的路径名
// 获取具体的静态方法的 id
jmethodID mid = env->GetStaticMethodID(jclz, "getUID", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid == NULL) {
return env->NewStringUTF("method no found!");
}
jstring id = env->NewStringUTF("xxxxxx");
jstring result = (jstring)env->CallStaticObjectMethod(jclz, mid, id);
return result;
}
- 说明:
与访问非静态不同的是,GetMethodID 方法换成了 GetStaticMethodID 方法,另外由 CallStaticObjectMethod 函数来调用具体的方法,前面两个参数是获得的类和方法 id,最后的参数是具体调用方法的参数(签名)。
GetStaticMethodID 方法的第一个参数就是具体的 Java 类型,第二个参数是该 Java 类的对应实例方法的名称,第三个参数就是该方法对应的返回类型和参数签名转换成 Native 对应的描述。对于不需要返回值的函数,调用 CallStaticVoidMethod 即可,对于返回值为引用类型的,调用 CallStaticObjectMethod 方法,对于返回基础类型的方法,则有各自对应的方法调用,比如:CallStaticBooleanMethod、CallStaticShortMethod、CallStaticDoubleMethod 等等
调用java类的构造方法
- java代码:
//访问构造方法
public native Date acceessConstructor();
- C++ 代码:
/*
* Class: JniMain
* Method: acceessConstructor
* Signature: (LAnimal;)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_JniMain_acceessConstructor
(JNIEnv *env, jobject job) {
//通过类的路径来从JVM里面找到对应的类
jclass jclz = env->FindClass("java/util/Date");
//jmethodId 构造方法
jmethodID jmid = env->GetMethodID(jclz, "","()V");
if (jmid == NULL) {
return NULL;
}
// 调用newObject 实例化Date 对象,返回值是一个jobject
jobject date_obj = env->NewObject(jclz, jmid);
// 得到对应的对象方法,前提是,我们访问了相关对象的构造函数创建了这个对象
jmethodID time_mid = env->GetMethodID(jclz, "getTime","()J");
jlong time = env->CallLongMethod(date_obj, time_mid);
printf("time: %lld \n", time);
return date_obj;
}
- 说明
调用java类的构造方法,等于在C++里面创建一个java对象,然后进行调用;同样也是采用GetMethodID的方法进行获取构造函数的id,然后由NewObject 进行对象的创建
JNI数组的使用
- java代码:
//整型数据在C++中进行排序
public native void giveArray(int[] inArray);
public native int[][] initInt2DArray(int size);
public native String[] initStringArray(int size);
//调用
int[] array = {3,9,2,50,6,13};
jniMain.giveArray(array);
for(int i=0; i< array.length; i++) {
System.out.println(array[i]);
}
String[] strArr = jniMain.initStringArray(5);
for (int i = 0; i < strArr.length; i++) {
System.out.println("strArr["+i+"] = "+strArr[i]);
}
int[][] intArr = jniMain.initInt2DArray(4);
for(int i =0; i < 4; i++) {
for(int j = 0; j < 3; j ++) {
System.out.println("arr["+i+"]["+j+"] = "+intArr[i][j]);
}
}
- C++代码:
/*
* Class: JniMain
* Method: giveArray
* Signature: ([I)V
*/
JNIEXPORT void JNICALL Java_JniMain_giveArray
(JNIEnv *env, jobject job, jintArray array) {
//jintArray -> jint *
jint *elemts = env->GetIntArrayElements(array, NULL);
if (elemts == NULL)
{
return;
}
//数组长度
int len = env->GetArrayLength(array);
qsort(elemts, len, sizeof(jint), compare);
//释放可能的内存
//将JNI 修改的数据重新写回原来的内存
env->ReleaseIntArrayElements(array, elemts, JNI_COMMIT);
}
/*
* Class: JniMain
* Method: initInt2DArray
* Signature: (I)[[I
*/
JNIEXPORT jobjectArray JNICALL Java_JniMain_initStringArray
(JNIEnv *env, jobject job, jint size) {
//创建jobjectArray对象
jobjectArray result;
jclass jclz;
int i;
jclz = env->FindClass("java/lang/String");
if (jclz == NULL) {
return NULL;
}
result = env->NewObjectArray(size,jclz, job);
if (result == NULL) {
return NULL;
}
//赋值
for ( i = 0; i < size; i++)
{
char * c_str = (char *)malloc(256);
memset(c_str, 0, 256);
//将 int 转换成为 char
sprintf(c_str, "hello num: %d\n",i);
// C -> jstring
jstring str = env->NewStringUTF(c_str);
if (str == NULL) {
return NULL;
}
// 将jstring 赋值给数组
env->SetObjectArrayElement(result, i, str);
free(c_str);
c_str = NULL;
}
return result;
}
/*
* Class: JniMain
* Method: initInt2DArray
* Signature: (I)[[I
*/
JNIEXPORT jobjectArray JNICALL Java_JniMain_initInt2DArray
(JNIEnv *env, jobject job, jint size) {
//返回对象,是个二维数组
jobjectArray ret;
int i = 0;
int j = 0;
jclass intArrayClz = env->FindClass("[I");
if (intArrayClz == NULL) {
return NULL;
}
ret = env->NewObjectArray(size * 3, intArrayClz, NULL);
jint tmp[3];//固定数组
for ( i = 0; i < size; i++)
{
jintArray intArr = env->NewIntArray(3);
for ( j = 0; j < 3; j++)
{
tmp[j] = i + j;
}
env->SetIntArrayRegion(intArr, 0, 3, tmp);
//将一维数组的值复制到
env->SetObjectArrayElement(ret, i, intArr);
env->DeleteLocalRef(intArr);
}
return ret;
}
- 说明:
jobjectArray
表示二维数组
env->SetObjectArrayElement(ret, i, intArr);
二维数组的赋值
env->NewObjectArray(size * 3, intArrayClz, NULL);
二维数组的创建
处理中文字符串的乱码问题
由于java的字符串编码,和C或者C++的字符串编码不一样,所以在java传中文到C/C++会出现乱码的现象
解决方法,就是在C/C++直接调用java的String来处理
- java代码:
//定义
public native String chineseChars2(String str);
//调用
System.out.println(jniMain.chineseChars2("宝宝22"));
- C++代码:
/*
* Class: JniMain
* Method: chineseChars2
* Signature: (LAnimal;)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_JniMain_chineseChars2
(JNIEnv *env, jobject job, jstring in) {
char * c_str = "马蓉与宝宝";
//创建String的类
jclass jclz = env->FindClass("java/lang/String");
//获取构造函数的mid 这边使用的是java的String(byte[], string)的构造函数
jmethodID mid = env->GetMethodID(jclz, "", "([BLjava/lang/String;)V");
//创建参数 jstring -> jbyteArray
jbyteArray bytes = env->NewByteArray(strlen(c_str));
// char * 赋值到byte数组中
env->SetByteArrayRegion(bytes, 0, strlen(c_str), (jbyte*)c_str);
// 设置编码
jstring charsetName = env->NewStringUTF("GB2312");
return env->NewObject(jclz, mid, bytes, charsetName);
}
JNI中 局部引用
- java代码:
public native void localRef();
- C++代码:
/*
* Class: JniMain
* Method: localRef 局部引用
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniMain_localRef
(JNIEnv *env, jobject job) {
int i;
for ( i = 0; i < 5; i++)
{
jclass clz = env->FindClass("java/util/Date");
jmethodID mid = env->GetMethodID(clz, "", "()V");
//创建对象
jobject date_obj = env->NewObject(clz, mid);
//使用这个引用
jmethodID time_mid = env->GetMethodID(clz, "getTime", "()J");
jlong time = env->CallLongMethod(date_obj, time_mid);
printf("local reference time: %lld \n", time);
//释放引用
env->DeleteLocalRef(clz);
env->DeleteLocalRef(date_obj);
}
}
- 说明:
局部引用
通过NewLocalRef和各种JNI接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等)。会阻止GC回收所引用的对象,不在本地函数中跨函数使用,不能跨线前使用。函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef释放
JNI中 全局引用
- java代码:
public native void createGlobalRef();
public native String getglobalRef();
public native void delGlobalRef();
- C++代码:
/*
* Class: JniMain
* Method: createGlobalRef 创建全局引用
* Signature: ()V
*/
jstring global_str;
JNIEXPORT void JNICALL Java_JniMain_createGlobalRef
(JNIEnv *env, jobject job) {
jstring str = env->NewStringUTF("JNI is intersting");
global_str = (jstring)env->NewGlobalRef(str);
}
//全局引用
//跨线程,跨方法使用
// NewGlobalRef 是创建全局引用的唯一方法
/*
* Class: JniMain
* Method: getglobalRef
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JniMain_getglobalRef
(JNIEnv *env, jobject job) {
return global_str;
}
/*
* Class: JniMain
* Method: delGlobalRef 移除全局引用
* Signature: ()V;
*/
JNIEXPORT void JNICALL Java_JniMain_delGlobalRef
(JNIEnv *env, jobject job) {
env->DeleteGlobalRef(global_str);
}
- 说明
全局引用
,变量是定义在方法外,调用NewGlobalRef基于局部引用创建,会阻GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放env->DeleteGlobalRef(g_cls_string);
JNI中 弱全局引用
- java代码:
public native String createWeakRef();
- C++代码:
/*
* Class: JniMain
* Method: createWeakRef
* Signature: ()Ljava/lang/String;
*/
jstring g_weak_cls;
JNIEXPORT jstring JNICALL Java_JniMain_createWeakRef
(JNIEnv *env, jobject job) {
jclass cls_string = env->FindClass("java/lang/String");
g_weak_cls = (jstring)env->NewWeakGlobalRef(cls_string);
g_weak_cls = env->NewStringUTF("Jni weak reference");
printf("weak ref = %s \n ",g_weak_cls);
return g_weak_cls;
}
- 说明:
弱全局引用
:调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef手动释放。env->DeleteWeakGlobalRef(g_cls_string)
JNI中 异常处理
- java代码:
public native void exception();
- C++代码:
/*
* Class: JniMain
* Method: exception 异常处理
* Signature: ()V;
*/
JNIEXPORT void JNICALL Java_JniMain_exception
(JNIEnv *env, jobject job) {
jclass cls = env->GetObjectClass(job);
jfieldID fid = env->GetFieldID(cls, "key11", "Ljava/lang/String;");
//检查是否发送异常
jthrowable ex = env->ExceptionOccurred();
// 判断异常是否发送
if (ex != NULL) {
jclass newExc;
//清空JNI 产生的异常
env->ExceptionClear();
//NullPointerException
newExc = env->FindClass("java/lang/IllegalArgumentException");
if (newExc == NULL)
{
printf("exception\n");
return;
}
env->ThrowNew(newExc, "Throw exception from JNI: GetFieldID faild ");
}
}
- 说明:
native调用java中的方法,java中的方法抛出异常,我们在native中检测异常,检测到后抛出native中的异常,并清理异常。
函数介绍:
1> ExceptionCheck:检查是否发生了异常,若有异常返回JNI_TRUE,否则返回JNI_FALSE
2> ExceptionOccurred:检查是否发生了异常,若用异常返回该异常的引用,否则返回NULL
3> ExceptionDescribe:打印异常的堆栈信息
4> ExceptionClear:清除异常堆栈信息
5> ThrowNew:在当前线程触发一个异常,并自定义输出异常信息
6> Throw:丢弃一个现有的异常对象,在当前线程触发一个新的异常
7> FatalError:致命异常,用于输出一个异常信息,并终止当前VM实例(即退出程序)
jni的静态注册和动态注册
参考:https://blog.csdn.net/qq_20404903/article/details/80662316
结语
以上就是个人对jni的认知和总结,如有错误的地方,欢迎大家指出,该篇文件比较适合于对JNI的入门的同学
项目代码:https://github.com/jasonkevin88/JniDemo