JNI学习(四)、本地方法创建java对象,以及对字符串的操作

时间:2023-01-18 04:02:56

一、本地方法创建Java对象

JNIEnv提供了下面的几个方法来创建一个Java对象:

jobject NewObject(jclass clazz, jmethodID methodID, ...) 
jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args)
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) {

对于这三个方法的区别,在JNI学习(三)中已经说得汗清楚了,它们只是传入形参的方式不一样,作用是一模一样的。

第一个参数jclass class  代表的你要创建哪个类的对象

第二个参数jmethodID methodID 代表你要使用哪个构造方法ID来创建这个对象。

只要有jclass和jmethodID ,我们就可以在本地方法创建这个Java类的对象。

指的一提的是:取得构造方法的jmethodID,与取得普通方法的jmethodID有一点不一样。

取得普通方法的jmethodID :

jmethodID id_date=env->GetMethodID(clazz,"show","(DD)V");
第二个参数指定的是要取得的普通方法的名称,第三个为方法签名,表示有两个double的参数,返回值为Void

但是构造方法的取得和普通方法不一样,因为构造方法的名称和类名是一样的,所以第二个参数统一指定为"<init>"。并且构造方法没有返回值,所以统一指定返回值签名为“V“

比如,我们要创建一个java.util.Date对象,那么可以使用下面的方式:

jclass clazz=env->FindClass("java/util/Date"); //取得java.util.Date类的jclass对象
jmethodID id_date=env->GetMethodID(clazz,"Date","()V"); //取得某一个构造方法的jmethodID
jobject date=env->NewObject(clazz,id_date);//调用NewObject方法创建java.util.Date对象

//获得Date对象后,就可以使用Date对象里面的方法了。
jmethodID id_getTime=env->GetMethodID(clazz,"getTime","()J");
    jlong time=env->CallLongMethod(date,id_getTime);
    cout<<time<<endl;


还有一种创建Java对象的方式,但是不常用:使用JNIEnv提供的函数AllocObject,该函数可以根据传入的jclass创建一个Java对象,但是他的状态时非初始化的,在使用这个对象之前绝对要用CallNonvirtualVoidMethod来调用该jclass的构造函数,这样可以延迟构造函数的调用。

比如:

jclass clazz=env->FindClass("java/util/Date");
jmethodID methodID_str=env->GetMethodID(clazz,"<init>","()V");
//预先创建一个没有初始化的字符
jobject date=env->AllocObject(clazz);
//调用构造方法
env->CallNonvirtualVoidMethod(date,clazz,methodID_str);
//使用它
jmethodID id_getTime=env->GetMethodID(clazz,"getTime","()J");
jlong time=env->CallLongMethod(date,id_getTime);
cout<<time<<endl;

二、对java字符串的操作

在Java中,使用的字符串String对象是Unicoode(UTF-16)码,即每个字符不论是中文还是英文还是符号,一个字符总是占用两个字节。

但是在C/C++中一个字符是一个字节,但是C/C++中的宽字符是两个字节的。Java通过JNI接口可以将Java的字符串转换到C/C++的宽字符串(wchar_t*),或是传回一个UTF-8的字符串(char*)到C/C++,反过来,C/C++可以通过一个宽字符串,或是一个UTF-8编码的字符串创建一个Java端的String对象。

可以看下面的一个例子:

在Java端有一个字符串 String string="abcde";,现在在本地方法中取得它并且输出:

JNIEXPORT void JNICALL Java_com_tao_test_Test_show
(JNIEnv * env, jobject obj)
{
jfieldID id_string=env->GetFieldID(env->GetObjectClass(obj),"string","Ljava/lang/String;");//取得该字符串的jfieldID
jstring string=(jstring)(env->GetObjectField(obj,id_string)); //取得该字符串,强转为jstring类型。
printf("%s",string);
}
然后再Java端调用该本地方法,最后输出结果为:

可以看到,从java端取得的String属性或者是从方法的String对象返回值,对应在JNI中都是jstring类型,它并不是C/C++中的字符串。jstring类型与C/C++中的字符串类型是不一样的, 所以,我们需要对取得的 jstring类型的字符串进行一系列的转换,才能使用。

JNIEnv提供了一系列的方法来操作字符串。

1、

const jchar *GetStringChars(jstring str, jboolean *isCopy)

将一个jstring对象,转换为(UTF-16)编码的宽字符串(jchar*)。

const char *GetStringUTFChars(jstring str, jboolean *isCopy)

将一个jstring对象,转换为(UTF-8)编码的字符串(char*)


这两个函数的参数含义:

第一个参数传入一个指向Java 中String对象的jstring变量

第二个参数传入的是一个jboolean的指针。可以使NULL,JNI_TRUE或者JNI_FLASE。如果为JNI_TRUE则表示开辟内存,然后把Java中的String拷贝到这个内存中,然后返回指向这个内存地址的指针。如果为JNI_FALSE,则直接返回指向Java中String的内存指针。这个时候千万不要改变这个内存中的内容,这将破坏String在Java中始终是常量的规则。如果是NULL,则表示不关心是否拷贝字符串,它就不会给jboolean*指向的内存赋值。


使用这两个函数取得的字符,在不适用的时候,要分别对应的使用下面两个函数来释放内存。

RealeaseStringChars(jstring jstr,const jchar* str)

RealeaseStringUTFChars(jstring jstr,const char* str)

第一个参数指定一个jstring变量,即要释放的本地字符串的资源

第二个参数就是要释放的本地字符串


2、

为了增加直接传回指向Java字符串的可能性(而不是拷贝),JDK1.2出来了新的函数GetStringCritical/RealeaseStringCritical

const jchar * GetStringCritical(jstring string, jboolean *isCopy)
void ReleaseStringCritical(jstring string, const jchar *cstring)

在GetStringCritical/RealeaseStringCritical之间是一个关键区,在这个关键区中绝对不能呼叫JNI的其他函数,也不能有会造成当前线程中断或是让当前线程等待的任何本地代码。否则将造成关键区代码执行期间垃圾回收器停止运作任何触发垃圾回收器的线程也会暂停,其他的垃圾回收器的线程不能前进直到当前的线程结束而激活垃圾回收器。

并且在关键区中千万不要出现中断操作,或是在JVM中分配任何新对象,否则会造成JVM死锁。

虽说这个函数会增加直接传回指向Java字符串的指针的可能性,不过还是会根据情况传回拷贝的字符串。

没有GetStringUTFCritical函数,因为Java字符串是UTF-16的编码,要转换为UTF-8编码的字符串始终要进行一次拷贝,所以不存在这样的函数。


3、

GetStringRegion  /  GetStringUTFRegion

这也是Java1.2新增的函数,这个函数的动作,是把Java字符串的内容直接拷贝到C/C++的字符数组中。在呼叫这个函数之前必须有一个C/C++分配出来的字符串,然后传入到这个函数中进行字符串的拷贝。

由于C/C++中分配内存开销相对小,而且Java中的String内容拷贝的开销可以忽略,更好的一点事次函数不分配内存,不会抛出OutOfMemoryError

 void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf)     //拷贝java字符串并且UTF-8编码传入buffer

void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) //拷贝java字符串并且UTF-16编码传入buffer


4、

jstring NewString(const jchar *unicode, jsize len)   //根据传入的宽字符串创建一个Java String对象

jstring NewStringUTF(const char *utf)    ////根据传入的UTF-8字符串创建一个Java String对象


5、

jsize GetStringLength(jstring jstr)   //返回一个java String对象的字符串长度

jsize GetStringUTFLength(jstring jstr) //返回一个java String对象 经过UTF-8编码后的字符串长度



三、处理Java的数组

我们可以使用GetFieldID获取一个Java数组变量的ID,然后用GetObjectFiled取得该数组变量到本地方法,返回值为jobject,然后我们可以强制转换为j<Type>Array类型。

可以明白Java的数组,在JNI中都是j<Type>Array的类型。具体的类型如下:如jbooleanArray,jbyteArray等等

typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

j<Type>Array类型是JNI定义的一个对象类型,它并不是C/C++的数组,如int[]数组,double[]数组等等。所以我们要把j<Type>Array类型转换为C/C++中的数组。

JNIEnv定义了一系列的方法来把一个j<Type>Array类型转换为C/C++数组,和把C/C++数组转换为j<Type>Array

 jsize GetArrayLength(jarray array) 

jobjectArray NewObjectArray(jsize len, jclass clazz,jobject init)
jobject GetObjectArrayElement(jobjectArray array, jsize index)
void SetObjectArrayElement(jobjectArray array, jsize index,jobject val)

jbooleanArray NewBooleanArray(jsize len)
jbyteArray NewByteArray(jsize len)
jcharArray NewCharArray(jsize len)
jshortArray NewShortArray(jsize len)
jintArray NewIntArray(jsize len)
jlongArray NewLongArray(jsize len)
jfloatArray NewFloatArray(jsize len)
jdoubleArray NewDoubleArray(jsize len)

jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy)
jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy)
jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy)
jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy)
jint * GetIntArrayElements(jintArray array, jboolean *isCopy)
jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy)
jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy)
jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy)

void ReleaseBooleanArrayElements(jbooleanArray array,jboolean *elems,jint mode)
void ReleaseByteArrayElements(jbyteArray array,jbyte *elems,jint mode)
void ReleaseCharArrayElements(jcharArray array,jchar *elems,jint mode)
void ReleaseShortArrayElements(jshortArray array,jshort *elems,jint mode)
void ReleaseIntArrayElements(jintArray array,jint *elems,jint mode)
void ReleaseLongArrayElements(jlongArray array,jlong *elems,jint mode)
void ReleaseFloatArrayElements(jfloatArray array,jfloat *elems,jint mode)
void ReleaseDoubleArrayElements(jdoubleArray array,jdouble *elems,jint mode)

 void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy)
 void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode)

void GetBooleanArrayRegion(jbooleanArray array,jsize start, jsize len, jboolean *buf)
void GetByteArrayRegion(jbyteArray array,jsize start, jsize len, jbyte *buf)
void GetCharArrayRegion(jcharArray array,jsize start, jsize len, jchar *buf)
void GetShortArrayRegion(jshortArray array,jsize start, jsize len, jshort *buf)
void GetIntArrayRegion(jintArray array,jsize start, jsize len, jint *buf)
void GetLongArrayRegion(jlongArray array,jsize start, jsize len, jlong *buf)
void GetFloatArrayRegion(jfloatArray array,jsize start, jsize len, jfloat *buf)
void GetDoubleArrayRegion(jdoubleArray array,jsize start, jsize len, jdouble *buf)

void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,const jboolean *buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,const jbyte *buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len,const jchar *buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len,const jshort *buf)
void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint *buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len,const jlong *buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,const jfloat *buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,const jdouble *buf)

上面是JNIEnv提供的所有操作数组的方法,大致可以分为下面几类。

1、获取数组的长度

 jsize GetArrayLength(jarray array) 
不管是基本类型数组,还是对象类型数组


2、对象类型数组的操作

 jobjectArray NewObjectArray(jsize len, jclass clazz,jobject init)
jobject GetObjectArrayElement(jobjectArray array, jsize index)
void SetObjectArrayElement(jobjectArray array, jsize index,jobject val)
JNI没有提供直接把Java的对象类型数组(Object[ ])直接转到C++中的jobject[ ]数组的函数。而是直接通过Get/SetObjectArrayElement这样的函数来对Java的Object[ ]数组进行操作

3、基本数据类型数组的操作

基本数据类型数组的操作方法比较多,大致可以分为如下几类:

①、Get<Type>ArrayElements/Realease<Type>ArrayElements

Get<Type>ArrayElements(<Type>Array arr,jboolean* isCopied);

这类函数可以把Java基本类型的数组转换到C/C++中的数组。有两种处理方式,一是拷贝一份传回本地代码,另一个是把指向Java数组的指针直接传回到本地代码,处理完本地化的数组后,通过Realease<Type>ArrayElements来释放数组

Realease<Type>ArrayElements(<Type>Array arr,<Type>* array,jint mode);用这个函数可以选择将如何处理Java跟C/C++的数组,是提交,还是撤销等,内存释放还是不释放

mode可以取下面的值:

0       对Java的数组进行更新并释放C/C++的数组

JNI_COMMIT    对Java的数组进行更新但是不释放C/C++的数组

JNI_ABORT    对Java的数组不进行更新,释放C/C++的数组


②、GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical

void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) {
void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode)

这两个函数也是JDK1.2 新增的,为了增加直接传回指向Java数组的指针而加入的函数,同样的,也会有用GetStringCritical的死锁的问题。


3、Get<Type>ArrayRegion/Set<Type>ArrayRegion

Get<Type>ArrayRegion(<Type>Array arr,jsize start,jsize len,<Type>* buffer)

在C/C++预先开辟一段内存,然后把Java基本类型的数组拷贝到这段内存中,跟GetStringRegion原理相似。

Set<Type>ArrayRegion(<Type>Array arr,jsize start,jsize len,const <Type>* buffer)

把java基本类型 的数组中的指定范围的元素用C/C++的数组中的元素来赋值


看下面的一个对基本数据类型的操作:

Java端代码如下,有一个本地方法和数组

package com.tao.test;

public class Test {
private int [] arrays=new int[]{1,2,3,4,5};
public native void show();
static{
System.loadLibrary("NativeTest");
}
public static void main(String[] args) {
new Test().show();
}
}
本地方法:

JNIEXPORT void JNICALL Java_com_tao_test_Test_show
(JNIEnv * env, jobject obj)
{
jfieldID id_arrsys=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
jintArray arr=(jintArray)(env->GetObjectField(obj,id_arrsys));
jint* int_arr=env->GetIntArrayElements(arr,NULL);
jsize len=env->GetArrayLength(arr);
for(int i=0;i<len;i++)
{
cout<<int_arr[i]<<endl;
}
env->ReleaseIntArrayElements(arr,int_arr,JNI_ABORT);
}
最后在Java中调用本地方法,输出了字符串。