通过jni可以让java和原生语言进行通信,这个通信不仅仅是信息传递,还包括方法间的调用,参数的传递。但是由于java的数据类型和原生语言的数据类型还是有所差异的,并且它们的实现机制不同,所以就需要将java中的对象和原生语言对象一一对应起来。在这个对应过程中,其实是很繁琐并且开销很大的,所以一般会SWIG自动生成代码。但是为了学习这个过程,我们必须知道整个转换的过程是怎样的。
本文不讲解jni项目的生成和调用过程,读者如果需要可以自行学习也可以参考:了解jni的调用流程。
在java中,数据类型可以分为两大类,一类是基本数据类型,一类是引用类型。
基本数据类型:bool,char,byte,short,int,long,float,double。
引用类型:除了基本类型以外的所有数据类型,包括数组。
通过jni的类型定义,可以让java的基本数据类型和原生语言的基本类型相映射。意思就是说,通过jni.h头文件,我们就可以通过jni头文件中定义的类型在原生语言实现中来操作java对象。jni中类型定义的java基本数据类型与原生语言的基本类型对应关系如下表:
我们要知道的就是JNI类型对应的JAVA类型即可。
对于java的引用类型,它的内部数据结构并不向原生代码公开,因此也就无法一一映射起来,但是在JNI类型中有一个jobject类型,基本是涵盖了所有的引用类型的。而接下来看到的引用类型隐射表,所对应的原声类型这一列对象全部都是jobject的子类。
对于以上的映射关系,我们必须清楚的知道它们所对应的java数据类型。
由于java的基本数据类型与原生语言的基本类型可以一一对应,那么在使用的时候就不需要进行转换了,直接使用对应的jni类型或原生语言类型。但是对于引用类型来说,它的转换比较复杂,所以必须了解他的转换过程。
由于引用类型对原生语言不是公开的,因此在使用的时候就必须进行转换了。所以接下来就会讲解各种转换情况。
java的字符串类型被原生语言当作引用类型来处理,因此如果想在原生语言中使用字符串类型的数据,就必须进行转换。JNI机制提供两种编码方式的的字符串格式,分别是Unicode和UTF编码格式。针对不同的编码格式有不同的转换方法。
可以将c风格字符串转换为jni类型的jstring然后返回给java对象使用。如果是Unicode编码则使用NewString,UTF编码则用NewStringUTF函数。比如:
env->NewStringUTF("来自C++");
java字符串在原生语言中被当作引用类型来处理,因此使用之前必须转换为char数组类型。如果是Unicode的编码使用GetStringChars,如果是UTF编码则使用GetStringUTFChars。比如:
const char *p;
jboolean isCopy = JNI_TRUE;
p = env->GetStringUTFChars(param, &isCopy);
#define JNI_FALSE 0 #define JNI_TRUE 1
穿件了字符串指针,使用完毕后应该释放从而避免内存泄漏。释放方法如果是Unicode编码使用releaseStringChars,如果是UTF编码使用releaseStringUTFChars。比如
env->ReleaseStringUTFChars(javaString, p);
由于数组也被当作引用对象,所以JNI也提供函数对数组进行处理。
通过New
jintArray array1 = env->NewIntArray(10);
通过Get
jint nativeArray[10]; //将java数组转换成c数组 env->GetIntArrayRegion(array, 0, 10, nativeArray);
由于通过Get
env->SetIntArrayRegion(array, 0, 10, nativeArray);
由于复制的代价很高,尤其是在数组元素很多的情况下,因此使用指针的方式会更加合理。通过Get
jint *pNative; jboolean isCopy=JNI_TRUE; pNative=env->GetIntArrayElements(array,&isCopy);
操作完成后,需要通过Release
env->ReleaseIntArrayElements(array,pnative,0);0表示释放模式。总共有三种释放模式:
复制回来的意思是,将原生数组的内容复制到java数组。
不仅java可以调用原生语言,原生语言同样可以调用java对象。在java中,类有两个域,分别是静态域和实例域。一个类可以有多个实例域,但是这多个实例域都对应者一个静态域。而在原生语言中获取它们的方法也是不同的。
要想获取类的对象的实例域或者静态域,就必须知道类。获取当前调用对象的类的方法如下:
jclass clazz; clazz = env->GetObjectClass(jTiss);
要想获取java对象的实例域或者静态域,除了知道所属的类以外,还必须知道它的域ID。根据实例域和静态域,获取的方法也有所不同。
实例域,比如:
jfieldID fieldID; fieldID = env->GetFieldID(clazz, "instanceString", "Ljava/lang/String;");其中第二个参数表示java类中的实例变量的名称,第三个参数是方法描述符,是Jni特有的一种。
静态域,比如:
jfieldID fieldID; fieldID = env->GetStaticFieldID(clazz, "staticString", "Ljava/lang/String;");
有了上述的域ID,就可以获取java的实例变量或者静态变量了。
获取实例变量:
jstring string; string = (jstring) env->GetObjectField(jTiss, fieldID);获取静态变量:
jstring string; string = (jstring) env->GetStaticObjectField(clazz, fieldID);
同样的首先我们必须知道所调用的对象的类,接着需要知道方法ID:
获取实例方法ID:
jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetMethodID(clazz, "getInstanceStringFromJava", "()Ljava/lang/String;");其中第二个参数是方法名称。
获取静态方法ID:
jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetStaticMethodID(clazz,"getStaticStringFromJava", "()Ljava/lang/String;");
有了方法ID就可以调用了。
调用实例方法:
env->CallObjectMethod(jThis, methodID);
env->CallStaticObjectMethod(clazz, methodID);
上面基本涵盖了最基础的知识点,下面通过例子练手。
给出头文件代码:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include源文件:#include #include #include /* Header for class com_example_jnitestthree_CppUtils */ #ifndef _Included_com_example_jnitestthree_CppUtils #define _Included_com_example_jnitestthree_CppUtils #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_jnitestthree_CppUtils * Method: getStringFromCPP * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStringFromCPP (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_example_jnitestthree_CppUtils_add (JNIEnv *, jobject, jint, jint); /* * Class: com_example_jnitestthree_CppUtils * Method: putJavaStringToJni * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_putJavaStringToJni (JNIEnv *, jobject, jstring); /* * Class: com_example_jnitestthree_CppUtils * Method: testArray * Signature: ([I)[I */ JNIEXPORT jintArray JNICALL Java_com_example_jnitestthree_CppUtils_testArray (JNIEnv *, jobject, jintArray); /* * Class: com_example_jnitestthree_CppUtils * Method: getInstanceString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getInstanceString (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: getStaticString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStaticString (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: callInstanceMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callInstanceMethodByNative (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: callStaticMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callStaticMethodByNative (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
#include/* Class: com_example_jnitestthree_CppUtils * Method: getStringFromCPP * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStringFromCPP( JNIEnv *env, jobject jThiss) { return env->NewStringUTF("来自C++"); } /* * Class: com_example_jnitestthree_CppUtils * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_example_jnitestthree_CppUtils_add(JNIEnv *env, jobject jThiss, jint num1, jint num2) { //基本类型的java和C++通过jni类型映射起来了,因此可以直接使用 return num1 + num2; } /* * Class: com_example_jnitestthree_CppUtils * Method: putJavaStringToJni * Signature: (Ljava/lang/String;)V */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_putJavaStringToJni( JNIEnv *env, jobject jThiss, jstring param) { const char *p; jboolean isCopy = JNI_TRUE; p = env->GetStringUTFChars(param, &isCopy); jstring javaString = env->NewStringUTF(p); env->ReleaseStringUTFChars(javaString, p); return javaString; } /* * Class: com_example_jnitestthree_CppUtils * Method: testArray * Signature: ([I)Ljava/lang/String; */ JNIEXPORT jintArray JNICALL Java_com_example_jnitestthree_CppUtils_testArray( JNIEnv *env, jobject jThiss, jintArray array) { jintArray array1 = env->NewIntArray(10); jintArray sumArray = env->NewIntArray(10); jint nativeArray[10], nativeArray1[10], nativeSumArray[10]; //将java数组转换成c数组 env->GetIntArrayRegion(array, 0, 10, nativeArray); env->GetIntArrayRegion(array1, 0, 10, nativeArray1); env->GetIntArrayRegion(sumArray, 0, 10, nativeSumArray); for (jint i = 0; i < 10; i++) { nativeArray1[i] = i + 1; nativeSumArray[i] = nativeArray[i] + nativeArray1[i]; } env->SetIntArrayRegion(sumArray, 0, 10, nativeSumArray); return sumArray; } /* * Class: com_example_jnitestthree_CppUtils * Method: getInstanceString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getInstanceString( JNIEnv *env, jobject jTiss) { //首先获取类对象 jclass clazz; clazz = env->GetObjectClass(jTiss); //获取要操作的对象的ID jfieldID fieldID; fieldID = env->GetFieldID(clazz, "instanceString", "Ljava/lang/String;"); jstring string; string = (jstring) env->GetObjectField(jTiss, fieldID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: getStaticString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStaticString( JNIEnv *env, jobject jTiss) { jclass clazz; clazz = env->GetObjectClass(jTiss); jfieldID fieldID; fieldID = env->GetStaticFieldID(clazz, "staticString", "Ljava/lang/String;"); jstring string; string = (jstring) env->GetStaticObjectField(clazz, fieldID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: callInstanceMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callInstanceMethodByNative( JNIEnv *env, jobject jThis) { jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetMethodID(clazz, "getInstanceStringFromJava", "()Ljava/lang/String;"); jstring string = (jstring) env->CallObjectMethod(jThis, methodID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: callStaticMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callStaticMethodByNative( JNIEnv *env, jobject jThis) { jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetStaticMethodID(clazz, "getStaticStringFromJava", "()Ljava/lang/String;"); jstring string = (jstring) env->CallStaticObjectMethod(clazz, methodID); return string; }
package com.example.jnitestthree; import java.io.ByteArrayOutputStream; public class CppUtils { static { System.loadLibrary("cppUtils"); } /** * 从CPP获取字符串 * * @return */ public native String getStringFromCPP(); /** * 进行加操作 * * @param num1 * @param num2 * @return */ public native int add(int num1, int num2); /** * 将数组传递给jni * * @param param */ public native String putJavaStringToJni(String param); /** * 测试数组 * * @param array * @return */ public native int[] testArray(int[] array); public String getArrayByString(int[] array) { StringBuilder sb = new StringBuilder(); int[] sum = testArray(array); for (int i = 0; i < sum.length; i++) { sb.append(sum[i] + ","); } return sb.toString(); } public String instanceString = "instanceString"; public static String staticString = "staticString"; /** * 原生语言调用的对象实例和静态实例 * * @return */ public native String getInstanceString(); public native String getStaticString(); /** * 原生语言调用的实例方法和静态方法 */ public String getInstanceStringFromJava() { return "instanceStringFromJava"; } public native String callInstanceMethodByNative(); public static String getStaticStringFromJava() { return "staticStringFromJava"; } public native String callStaticMethodByNative(); }
运行结果: