1、前言
通过jni可以让java和原生语言进行通信,这个通信不仅仅是信息传递,还包括方法间的调用,参数的传递。但是由于java的数据类型和原生语言的数据类型还是有所差异的,并且它们的实现机制不同,所以就需要将java中的对象和原生语言对象一一对应起来。在这个对应过程中,其实是很繁琐并且开销很大的,所以一般会SWIG自动生成代码。但是为了学习这个过程,我们必须知道整个转换的过程是怎样的。
本文不讲解jni项目的生成和调用过程,读者如果需要可以自行学习也可以参考:
了解jni的调用流程。
2、数据类型的类别
在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类型或原生语言类型。但是对于引用类型来说,它的转换比较复杂,所以必须了解他的转换过程。
3、对引用类型进行操作
由于引用类型对原生语言不是公开的,因此在使用的时候就必须进行转换了。所以接下来就会讲解各种转换情况。
3.1 String类型的处理
java的字符串类型被原生语言当作引用类型来处理,因此如果想在原生语言中使用字符串类型的数据,就必须进行转换。JNI机制提供两种编码方式的的字符串格式,分别是Unicode和UTF编码格式。针对不同的编码格式有不同的转换方法。
3.1.1 将C风格字符串转换为java字符串
可以将c风格字符串转换为jni类型的jstring然后返回给java对象使用。如果是Unicode编码则使用NewString,UTF编码则用NewStringUTF函数。比如:
env->NewStringUTF("来自C++");
3.1.2 在原生语言中使用java字符串
java字符串在原生语言中被当作引用类型来处理,因此使用之前必须转换为char数组类型。如果是Unicode的编码使用GetStringChars,如果是UTF编码则使用GetStringUTFChars。比如:
<span style="white-space:pre"> </span>const char *p;
jboolean isCopy = JNI_TRUE;
p = env->GetStringUTFChars(param, &isCopy);
在上面由于GetStringUTFChars方法返回一个指向char数组首地址的指针,因此需要定义一个char指针变量。它的第二个参数表示返回的指针指向的是堆中的对象还是字符串副本,true表示指向副本。JNI类型定义了两个常量代表jboolean的真假,分别是
#define JNI_FALSE 0
#define JNI_TRUE 1
3.1.3 释放字符串
穿件了字符串指针,使用完毕后应该释放从而避免内存泄漏。释放方法如果是Unicode编码使用releaseStringChars,如果是UTF编码使用releaseStringUTFChars。比如
env->ReleaseStringUTFChars(javaString, p);
javaString指的是java字符串对象,p指的是签名创建的字符指针对象。
3.2 数组的处理
由于数组也被当作引用对象,所以JNI也提供函数对数组进行处理。
3.2.1 创建java数组
通过New<type>Array函数就可以在原生语言中创建java数组,type指的是Bool,Int,Float等基本数据类型。比如:
jintArray array1 = env->NewIntArray(10);
10表示数组的容量。
3.2.2 访问java数组元素
通过Get<type>ArrayRegion函数就可以将java数组元素复制到一个C数组中,从而可以在原生语言中对其进行操作。比如:
jint nativeArray[10];
//将java数组转换成c数组
env->GetIntArrayRegion(array, 0, 10, nativeArray);
这样,我们就可以操作nativeArray来访问java数组的元素了。注意这里是复制的方式,虽然访问的内容是一样的,但是他们所代表的对象不是同一个对象,而是java数组的一个副本。
3.2.3 将c数组复制到java数组
由于通过Get<type>ArrayRegion函数获取的只是java数组的副本,因此任何对c数组的操作都不会影响java数组,而如果希望将c数组操作后的结果复制会Java数组,就需要使用set<type>ArrayRegion函数。比如:
env->SetIntArrayRegion(array, 0, 10, nativeArray);
3.2.4 通过指针操作java数组
由于复制的代价很高,尤其是在数组元素很多的情况下,因此使用指针的方式会更加合理。通过Get<type>ArrayElements函数就可以获取一个指向java数组的指针。比如:
jint *pNative;
jboolean isCopy=JNI_TRUE;
pNative=env->GetIntArrayElements(array,&isCopy);
3.2.5 释放指针对象
操作完成后,需要通过Release<Type>ArrayElements函数释放指针。比如:
env->ReleaseIntArrayElements(array,pnative,0);
复制回来的意思是,将原生数组的内容复制到java数组。
3.3 原生语言访问java变量
不仅java可以调用原生语言,原生语言同样可以调用java对象。在java中,类有两个域,分别是静态域和实例域。一个类可以有多个实例域,但是这多个实例域都对应者一个静态域。而在原生语言中获取它们的方法也是不同的。
3.3.1 根据调用对象获取类
要想获取类的对象的实例域或者静态域,就必须知道类。获取当前调用对象的类的方法如下:
jclass clazz;
clazz = env->GetObjectClass(jTiss);
有了类,我们就可以获取域ID了。
3.3.2 获取域ID
要想获取java对象的实例域或者静态域,除了知道所属的类以外,还必须知道它的域ID。根据实例域和静态域,获取的方法也有所不同。
实例域,比如:
jfieldID fieldID;
fieldID = env->GetFieldID(clazz, "instanceString", "Ljava/lang/String;");
静态域,比如:
jfieldID fieldID;
fieldID = env->GetStaticFieldID(clazz, "staticString",
"Ljava/lang/String;");
参数同上。但是此方法获取的java对象的静态变量。
3.3.3 获取java变量的值
有了上述的域ID,就可以获取java的实例变量或者静态变量了。
获取实例变量:
jstring string;
string = (jstring) env->GetObjectField(jTiss, fieldID);
jstring string;
string = (jstring) env->GetStaticObjectField(clazz, fieldID);
3.4 原生语言调用java方法
同样的首先我们必须知道所调用的对象的类,接着需要知道方法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);
4、以实际例子总结以上知识点
上面基本涵盖了最基础的知识点,下面通过例子练手。
给出头文件代码:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 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 <com_example_jnitestthree_CppUtils.h>
/* 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;
}
java原生函数声明:
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();
}
运行结果: