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

image
在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

posted @ 2021-03-14 19:49  Jamgun  阅读(330)  评论(0编辑  收藏  举报