在c/c++本地代码中访问java的String对象
.在java中,使用的字符串String对象是Unicode码,即每个字符不论是中文还是英文或是符号,一个字符总是占用两个字节。
在c/c++本地代码中创建java的String对象
.java通过JNI接口可以将java的字符串转换到c/c++中的宽字符串(wchar_t *),或是传回一个UTF-8的字符串(char *)到c/c++。反过来,c/c++可以通过一个宽字符串,或是一个UTF-8编码的字符串来创建一个java端的String对象。
GetStringChars/GetStringUTFChars
.这两个函数用来取得与某个jstring对象相关的java字符串。分别可以取得UTF-16编码的宽字符串(jchar*)跟UTF-8编码的字符串(char*)。
Const jchar* GetStringChars(jstring str, jboolean* copied)
Const char* GetStringUTFChars(jstring str, jboolean* copied)
第一个参数传入一个指向java中的String对象的jstring变量
第二个参数传入的是一个jboolean的指针。
这两个函数分别都会有两个不同的动作:
第一个参数:
1、 开新内存,然后把java中的String拷贝到这个内存中,然后返回这个内存地址的指针。
2、 直接返回指向java中string的内存的指针,这个时候千万不要改变这个内存的内容,这将破坏String在java中始终是常量这个原则。
第二个参数:是用来标示是否对java的string对象进行了拷贝的。
如果传入的这个jboolean指针不是null,则他会给该指针指向的内存传入JNI_TRUE或JNI_FALSE标示是否进行了拷贝。
传入null标示不关心是否拷贝字符串,它就不会给jboolean*指向的内存赋值。
使用这两个函数取得的字符串,在不使用的时候,要使用ReleaseStringChars/ReleaseStringUTFChars来释放拷贝的内存,或是释放对java的String对象的引用。
ReleaseStringChars(jstring jstr, const jchar* str);
ReleaseStringUTFChars(jstring jstr, const char* str);
第一个参数指定一个jstring变量,即是要释放的本地字符串的来源。
第二个参数就是要释放的本地字符串
GetStringCritical:是为了增加直接传回指向java字符串的指针的可能性(而不是拷贝),jdk1.2出来了新的函数:GetStringCritical/ReleaseStringCritical。
Const jchar* GetStringCritical(jstring str, jboolean* copied)
Void realeaseStringCritical(jstring jstr, const jchar* str);
在GetStringCritical/RealeaseStringCritical之间是一个关键区。在这关键区之中绝对不能呼叫JNI的其他函数和会造成当前线程中断或是会让当前线程等待的任何本地代码,否则将造成关键区代码执行区间垃圾回收器停止运作,任何触发垃圾回收器的线程也会暂停。其他的触发垃圾回收器的线程不能前进直到当前线程结束而激活垃圾回收器。
在关键区中千万不雅出现中断操作,或是在jvm中分配任何新对象。否则会造成jvm死锁。
虽说这个函数会增加直接传回指向java字符串的指针的可能性,不过还是会根据情况传回拷贝过的字符串。
不支持GetStringUTFCritical,没有这样的函数。由于java字符串用的是UTF16,要转成UTF8编码的字符串始终需要进行一个拷贝。所以没有这样的函数。
GetStringRegion/GetStringUTFRegion
.java1.2出来的函数,这个函数的动作,是把java字符串的内容直接拷贝到c/c++的字符数组中。在呼叫这个函数之前必须有一个c/c++分配出来的字符串,然后传入到这个函数中进行字符串的拷贝。
由于c/c++中分配内存开销相对小,而且java中的String内容拷贝的开销可以忽略,更好的一点是此函数不分配内存,不会抛出OutOfMemoeryError异常。
//拷贝java字符串并以UTF-8编码传入buffer.
GetStringUTFRegion(String str, jsize start, jsize len, char* buffer);
//拷贝java字符串并以UTF-16编码传入buffer
GetStringRegion(jstring str, jsize start, jsize len, jchar* buffer);
其他的字符串函数:
.jstring NewString(const jchar* str, jszie len); 宽字符串(c/c++中的普通的字符串)
.jstring NewStringUTF(const char* str);
.jsize GetStringLength(jstring str);
.jsize GetStringUTFLength(jstring str);
例如:红线圈住的是对应的方法应用。Message是定义在java类中,在此省略了代码
处理数组:数组分为两种(基本类型的数组、对象类型的数组)
一个能通用于两种不同类型数组的函数:GetArrayLength(jarray array)
处理--基本类型的数组:
.处理基本类型的数组跟处理字符串类似,也有很相似的函数。
.Get<TYPE>ArrayElements(<TYPE>Array arr, jboolean* isCopied);这类的函数可以把java基本类型的数组转换到c/c++中的数组。有两种处理方式,一是拷贝一份传回本地代码,另一个是把指向java数组的指针传回到本地代码。处理完本地化的数组后,通过Release<TYPE>ArrayElements来释放数组。
.Release<TYPE>ArrayElements(<TYPE>Array arr, <TYPE>* array, jint mode);用这个函数可以选择将如何处理java跟c++的数组。是提交,还是撤销等,内存释放还是不释放。mode可以去下面的值:0(对java的数组进行更新并释放c/c++的数组)
JNI_COMMIT(对java的数组进行更新但不释放c/c++的数组)
JNI_ABORT (对java的数组不进行更新,释放c/c++的数组)
.GetPrimitiveArrayCritical(jarray arr, jboolean* isCopied);
ReleasePrimitiveArrayCritical(jarray arr, void* array, jint mode);它们也是jdk1.2出来的,为了增加直接传回指向java数组的指针而加入的函数。同样的,也会有同GetStringCritical的死锁的问题。
.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++的数组中的元素来赋值。
.<TYPE>Array New<TYPE>Array(jsize size);指定一个长度然后返回相应的java基本类型的数组。
处理—对象类型的数组【Object[]】:
JNI没有提供直接把java的对象类型数组(Object[])直接转到c/c++中的jobject[]数组的函数;而是直接通过GetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index)/ SetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index, jobject val)这样的函数来对java的Object[]数组进行操作。
.使用上述的函数也不用释放任何资源。
.NewObjectArray(jsize len, jclass clazz, jobject init)可以通过指定长度跟初始值来创建某个类的数组。
int[] arrays = {1,2,3,4,5,6,7,8,9,0};
public native void callCppFunction();
public static void main(String[] args) {
MainTest obj = new MainTest();
obj.callCppFunction();//这里会去访问数组。
for(int each: obj.arrays) {
System.out.println(each);
}
}
全局引用/局部引用/弱全局引用
.从java虚拟机创建的对象传到本地c/c++代码时会产生引用。根据java的垃圾回收机制,只要引用存在就不会触发该引用指向的java对象的垃圾回收。
.这些引用在JNI中分为三种:
局部引用(Local Reference);最常见的引用类型。基本上通过JNI返回来的引用都是局部引用。
例如使用NewObject就会返回创建出来的实例的局部引用。局部引用只在该Native函数中有效。所有在该函数中产生的局部引用,都会在函数返回的时候自动释放。也可以使用deleteLocalRef函数手动释放该引用。
注意:既然局部引用能够在函数返回时自动释放,为什么还需要deleteRef()函数呢?
实际上局部引用存在,就会防止其指向的对象被垃圾回收。尤其是当一个局部引用指向一个庞大的对象,或是在一个循环中生成了局部引用,最好的做法就是在使用完该对象后,或在该循环尾部把这个引用释放掉,以确保在垃圾回收器被触发的时候被回收。
在局部引用的有效期中,可以传递到别的本地函数中,要强调的是它的有效期仍然只在一次的java本地函数调用中,所以千万不能用c++全局变量保存它或是把它定义为c++静态局部变量。
全局引用(Global Reference);
全局引用可以跨越当前线程,在多个native函数中有效,不过需要编程人员手动来释放该引用。全局引用存在期间会防止在java的垃圾回收器中回收。
与局部引用不同的是,全局引用过的创建不是由JNI自动创建的,全局引用是需要调用NewGlobalRef函数,而释放它需要使用ReleaseGlobalRef函数。
弱全局引用(Weak Global Reference);
其是jdk1.2出来的功能,与全局引用相似,创建跟删除都需要编程人员手动来进行。这种引用与全局引用一样可以在多个本地代码有效,也跨越多线程有效。不一样的是,这种夹棍不会阻止垃圾回收器回收这个引用所指向的对象。
使用NewWeakGlobalRef跟ReleaseGlobalRef来产生和接触引用。
关于的一些函数:jobject NewGlobalRef(jobject obj);
Jobject NewLocalRef(jobject obj);
Jobject NewWeakGlobalRef(jobject obj);
Void DeleteGlobalRef(jobject obj);
Void DeleteLocalRef(jobject obj);
Void DeleteWeakGlobalRef(jobject obj);
Jboolean IsSameObject(jobject obj1, jobject obj2);这个函数对于弱全局引用还有一个特别的功能:把null传入要比较的对象中,就能够判断弱全局引用所指向的java对象是否被回收。《java native interface》
缓存JfieldID/jmethodID
.取得jfieldID和jmethodID的时候会通过该属性/方法名称上签名来查询相应的jfieldID/jmethodID.这种查询相对来说开销较大。其实,可以将这些FieldID/MethodID缓存起来,这样只需查询一次,以后就是使用缓存起来的FieldID/MethodID了。
介绍两种缓存方式的实现:
1、 在用的时候缓存:
.在native code中使用static局部变量来保存已经查询过的id,这样就不会在每次的函数调用是查询,而只要第一次查询成功后就保存起来了。
.不过在这种情况下不得不考虑多线程同时呼叫此函数时可能会造成同时查询的情况。不过这种情况下没必要担心,所以是无害即是线程安全的。因为返回同一个ID值
Static fieldID fieldID_string = null;
Jclass clazz = env->GetObjectClass(obj);
If(fieldID_string == null)
{fieldID_string = envo->GetFieldID(clazz, “string”, “Ljava/lang/String;”);
…
2、 在java类初始化是缓存
.更好的方法就是在任何native函数调用钱把id全部存起来。
.可以在第一次加载这个类的时候首先调用本地代码初始化所有的jfieldID/jmethodID,这样的话就可以省去多次得确定ID是否存在的语句,当然,这些jfieldID/jmethodID是定义在c/c++的全局。
.当java类卸载或重新加载的时候也会重新呼叫给本地代码来重新计算缓存的ID集。
(篇二完,后期篇是关于JNI异常处理/多线程/c/c++如何启动jvm(上面都没有涉及,在后期博客会更新,因为看英文的书籍,翻译和理解又慢点。)