JNI 入门教程
JNI 简介
- 全称:JNI 即 Java Native Interface(Java本地接口)
- 用途:Java与C/C++、汇编等本地代码集成
- 场景:
- Java运行效率相对C/C++要低
- 可以调用优秀的C/C++开源库
- class文件安全性较差,用C/C++实现反编译比较困难,安全性较高
- 扩展JVM的能力,让Java代码可以调用驱动
本文主要介绍Java调用本地C++代码
Hello World!
JNI可以看做一套协议,用于Java和C++之间的通信
完成Java调用C++需要三步:
- Java方法带上native声明
- 利用Java代码生成C++头文件
- 实现C++方法,并生成动态链接库(linux为.so文件,MacOS为.jnilib文件,Windows为.dll文件)
接下来会在Mac上,手把手实现一个Hello world教程
步骤一
Java方法带上native声明
package com.jni;
public class HelloWorld {
public native void hello();
}
步骤二
利用Java代码生成C++头文件
javac com/jni/HelloWorld.java
javah com.jni.HelloWorld
步骤三
实现C++方法,并生成动态链接库
-
实现C++方法
#include"com_jni_HelloWorld.h" JNIEXPORT void JNICALL Java_com_jni_HelloWorld_hello (JNIEnv *env, jobject jobj) { printf("Hello World!\n"); }
-
生成动态库
g++ -std=c++11 -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/include/darwin -fPIC main.cc -o libhelloworld.jnilib
此处linux,mac和win有不同,且和本地环境有关,坑较多,但目标均是生成动态链接库,有问题可自行Google
-
Java调用该方法并运行
public class HelloWorld { static{ //设置动态库查找路径 System.setProperty("java.library.path", "."); System.loadLibrary("helloworld"); } public native void hello(); public static void main(String[] args) { new HelloWorld().hello(); } }
javac com/jni/HelloWorld.java java -Djava.library.path=. com.jni.HelloWorld > Hello World!
此处
-Djava.library.path=.
和System.setProperty("java.library.path", ".");
的作用是一致的
基本类型传输
JNI可以看做一套协议,用于Java和C++之间的通信
搭通了Java调用C++的基本流程,接下来我们将详细介绍,Java和C++基本类型传输的协议
Java类型 | C++类型 |
---|---|
boolean | jboolean |
byte | jbtye |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
Object | jobject |
Class | jclass |
String | jstring |
基本的对应关系见上,未出现类型的对象均对应jobject
除最后三个对应关系外,以上对应关系均可进行强制类型转换
关于string
// jstring -> string
inline const char* JstringToString(JNIEnv *env, jstring j) {
return env->GetStringUTFChars(j, nullptr);
}
// string -> jstring
inline jstring StringToJstring(JNIEnv *env, string str) {
return env->NewStringUTF(str.c_str());
}
常用数据结构 和 对象传输
int数组
-
jintArray -> int[]
int arraySize = (int) env->GetArrayLength(_jintArray); jint *pointer = env->GetIntArrayElements(_jintArray, NULL); jint _jint[arraySize]; for (int i = 0; i < arraySize; i++) { _jint[i] = pointer[i]; } env->ReleaseIntArrayElements(_jintArray, pointer, 0);
-
其余基本类型数组类似,可Google得到相应的细节
数组
-
jobjectArray -> jobject
// array size inline int GetObjArrayLength(JNIEnv *env, jobjectArray _jobjectArray) { return env->GetArrayLength(_jobjectArray); } // array element inline jobject GetObjArrayElement(JNIEnv *env, jobjectArray _jobjectArray, int index) { return env->GetObjectArrayElement(_jobjectArray, index); }
-
new jobjectArray
// new string array inline jobjectArray newJobjectArray(JNIEnv *env, jclass _jclass, int len) { return env->NewObjectArray(len, _jclass, 0); } // set element inline void setElementToArray(JNIEnv *env, jobjectArray _jobjectArray, int rank, jobject _jobject) { env->SetObjectArrayElement(_jobjectArray, rank, _jobject); }
list
-
jlist -> list
//arraylist相关 struct ArrayList_Class_Method{ jclass alist_class; jmethodID alist_get; jmethodID alist_size; }; //初始化ArrayList_Class_Method void GetArrayListClassMethod(JNIEnv *env, ArrayList_Class_Method* arrayList_class_method, jobject jalist) { arrayList_class_method->alist_class = env->GetObjectClass(jalist); //获取Arraylist的methodid arrayList_class_method->alist_get = env->GetMethodID(arrayList_class_method->alist_class, "get", "(I)Ljava/lang/Object;"); arrayList_class_method->alist_size = env->GetMethodID(arrayList_class_method->alist_class, "size", "()I"); } //获取list的长度 inline int GetArrayListLen(JNIEnv *env, ArrayList_Class_Method arrayList_class_method, jobject jalist) { return (int) env->CallIntMethod(jalist, arrayList_class_method.alist_size); } //获取list的元素 inline jobject GetArrayListObject(JNIEnv *env, ArrayList_Class_Method arrayList_class_method, jobject jalist, int index) { return env->CallObjectMethod(jalist, arrayList_class_method.alist_get, index); }
-
new jlist
struct ArrayList_Class_Build{ jclass alist_class; jmethodID alist_init; jmethodID alist_add; }; void GetArrayListClassBuild(JNIEnv *env, ArrayList_Class_Build* arrayList_class_build) { //获取类 arrayList_class_build->alist_class = env->FindClass("java/util/ArrayList"); //获取ArrayList构造函数id arrayList_class_build->alist_init = env->GetMethodID(arrayList_class_build->alist_class, "<init>", "()V"); //获取ArrayList对象的add()的methodID arrayList_class_build->alist_add = env->GetMethodID(arrayList_class_build->alist_class, "add", "(Ljava/lang/Object;)Z"); } //new jobject inline jobject NewJArrayList(JNIEnv *env, ArrayList_Class_Build arraylist_class_build) { return env->NewObject(arraylist_class_build.alist_class, arraylist_class_build.alist_init, ""); } // 添加alist inline void AddJArrayList(JNIEnv *env, jobject jalist, ArrayList_Class_Build arraylist_class_build, jobject jelement) { env->CallBooleanMethod(jalist, arraylist_class_build.alist_add, jelement); }
一般对象传输
-
Java类型
public class People { int age; String name; }
-
Jobject -> c++
struct Class_Field{ jclass people_class; jfieldID people_age_field; jfieldID people_name_field; }; void GetClassField(JNIEnv *env, Class_Field* class_field) { class_field->people_class = env->FindClass("com/jni/People") class_field->vertex_age_field = env->GetFieldID(class_field->people_class, "age", "I"); class_field->vertex_name_field = env->GetFieldID(class_field->people_class, "name", "Ljava/lang/String;"); } inline int GetPeopleAge(JNIEnv *env, Class_Field class_field, jobject _jobject) { return env->GetIntField(_jobject, class_field.people_age_field); } inline jsrting GetPeopleName(JNIEnv *env, Class_Field class_field, jobject _jobject) { return env->GetObjectField(_jobject, class_field.people_name_field); }
具体的field声明见下
Java类型 符号 boolean Z byte B char C short S int I long J float F double D void V object LClassName; L类名; Array [array-type [数组类型 Method (argument-types)return-type (参数类型)返回类型 -
new object
inline jobject NewJPeople(JNIEnv *env, Class_Field class_field) { return env->AllocObject(class_field.people_class); } env->SetIntField(_jobject, class_field.people_age_field, _int); env->SetObjectField(_jobject, class_field.people_name_field, StringToJstring(env, _string));
-
注,基本类型都有对应的set/get field函数,一般性类型可以使用set/get object field
内存泄漏问题
结论:
在C/C++中实例化的JNI对象,如果不返回给Java代码部分,必须用release掉或delete,否则内存泄露
原因:
每当在 Native代码中引用到一个Java对象时,JVM 就会在Local Reference Table中创建一个Local Reference
。比如,我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference Table
中就会相应新增一个 Local Reference
。
在Native Method结束时,JVM会根据引用计数释放JNI 对象,Local Reference Table
是有大小限制的,在开发中应该及时使用DeleteLocalRef()删除不必要的Local Reference,不然可能会出现溢出错误
这样需要手动减少引用计数的情况如下:
NewStringUTF,NewObject,FindClass等方法; jstring,jobject ,jobjectArray,jclass等对象
不需要手动减少引用计数的有:jint , jlong , jchar,jmethodID,jfieldID等
其中string的情况较复杂
// 创建 jstring 和 char*
jstring jinfo = (jstring) env->GetObjectField(jpeople, class_field.people_name_field);
const char* name = env->GetStringUTFChars(jinfo, nullptr);
// 释放
env->ReleaseStringUTFChars(jinfo, name);
env->DeleteLocalRef(jinfo);
其余情况只需env->DeleteLocalRef(jobj);
总结一句话,凡是不是C++函数参数或者返回值的jobject,而是自己声明的jobject均需要DeleteLocalRef
后续会继续补充更多细节
如有问题可联系13011598178@qq.com