第5课
1. 在本地代码中,创建String对象
jstring NewString(const jchar* str,jsize len); //传入一个宽字符串及长度,就能创建一个java的string对象
jstring NewStringUTF(const char* str); //传入一个UTF-8格式的字符串就可以
为什么不用传入字符串长度呢?C/C++中字符串都是以'/0'结尾的,通过这个‘/0’就能知道字符串的长度了。
jsize GetStringLength(jstring str);
jsize GetStringUTFLength(jstring str); //字符串以UTF-8格式存储时占用多少个字节。
代码示例:
MainTest.java
第6课 处理数组
数组分为两种:
1.基本类型的数组
2.对象类型(Object [])的数组
1. Get<TYPE>ArrayElements(<TYPE>Array arr, jboolean* isCopied);
这个函数可以把java基本数组转换到C/C++中的数组,有两种处理方式,一是拷贝一份传回本地代码,另一个是把指向java数组的指针直接传回到本地代码。
处理完成本地化的数组后,通过 Release<TYPE>ArrayElements来释放数组。
0 ===》 对java的数组进行更新并释放C/C++中的数组
JNI_COMMIT ===》对java的数组进行更新但不释放C/C++中的数组
JNI_ABIRT ===》 对java的数组不进行更新,释放C/C++中的数组
GetPrimitiveArrayCritical(jarray arr,jboolean* isCopied);
ReleasePrimitiveArrayCritical(jarray arr,void* array,jint mode);
为了增加直接会加指向java数组的指针而加入的函数,同样的,也会有死锁的问题。
2.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,<TYPE>* buffer);
把java基本类型的数组中的指定范围的元素内 C/C++ 的数组中的元素来赋值。
3.<TYPE>Array New<TYPE>Array(jsize sz);
指定一个 长度然后返回相应java基本类型的数组。
处理数组--对象类型数组 (Object [ ])
1. JNI没有提供直接把Java的对象类型数组(Object[])直接转到C++中的jobject[]数组的函数。而是直接 通过Get/SetObjectArrayElement这样的函数来对Java的Object[]数组进行操作。
jobject GetObjectArrayElement(jobjectArray array,jsize index);
void SetObjectArrayElement(jobjectArray array,jsize index,jobject val);
使用以上函数不用释放 任何资源。
2. NewObjectArray 可以通过指定长度跟初始值来创建某个类的数组。
代码示例:在本地代码中访问java中的基本数组
TestNative.java
全局引用(Global Reference)
局部引用(Local Reference)
弱全局引用(Weak Global Reference)
2. 局部引用
最常见的引用类型,基本上通过JNI返回来的引用都是局部引用。
例如使用 NewObject 就会返回创建出来的实例的局部引用。局部引用只在该 native 函数中有效,所有在该函数中产生的局部引用,都会在函数返回的时候自动释放(freed)。也可以用DeleteLocalRef函数手动释放该引用。
想一想既然局部引用能够在函数返回时自动释放,为什么还需要DeleteLocalRef函数呢?
实际上局部引用存在,就会防止其指向的对象被垃圾回收,尤其是当一个局部引用指向一个很庞大的对象 或是在一个循环中生成了局部引用,最好的做法就是在使用完该对象后,或在该循环尾部把这个引用释放掉,以确保在垃圾回收器被触发的时候被回收。
在局部引用的有效期中,可以传递到别的本地函数中,要强调的是他的有效期仍然只在一次的Java本地函数中,所以千万不能用C++全局变量保存他或是把他定义为C++静态局部变量。
3.全局引用
全局引用可以跨越当前线程,在多个native函数中有效,不过需要工程师手动来释放该引用。全局引用存在期间会防止在Java的垃圾回收的回收。
与局部引用不同,全局引用的创建不是由JNI自动创建的,全局引用是需要调用NewGlobalRef函数,而释放他需要使用ReleaseGlobalRef函数。
4.弱全局引用
与全局引用相似,创建跟删除都需要由工程师来进行,这种引用与全局引用一样可以在多个本地代码有效,也跨越多个线程有效。不同的是,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象。
使用NewWeakGloabalRef跟RelwaseWeakGlobalRef来产生和解除引用。
5.关于使用的一些函数:
jobject NewGlobalRef(jobject obj);
jobject NewGlobalRef(jobject obj);
void DeleteGlobalRef(jobject obj);
void DeleteLocalRef(jobject obj);
void DeleteWeakGlobalRef(jobject obj);
jboolean IsSameObject(jobject obj1, jobject obj2) ; //判断obj1,obj2是否指向同一个对象。
这个函数对于弱全局引用还有一个特殊的功能,把NULL传入要比较的对象中,就能够判断弱全局引用所指向的Java对象是否被回收。
6.缓存jfieldID/jmethodID
取得 jfieldID跟jmethodID的时候会通过该属性/方法名称加上签名来查询相应的jfieldID/jmethodID.
这种查询相对来说开销较大,我们可以将这些jfieldID/jmethodID缓存起来,这样只需要查询一次,以后就使用缓存起来的jfieldID/jmethodID,
下面介绍两种缓存方式:
1.在用的时候缓存
2.在java类初始化时缓存
1.在第一次使用的时候缓存
在native code中使用static局部变量来保存已经查询过的id.这样就不会在每次的函数调用时查询.
不过这种情况下就不得不考虑多线程同时呼叫此函数时可能会招致同时查询的危机.
更好的一个方式就是在任何native函数调用前把id全部存在来.
我们可以让java在第一次加载这个类的时候首先调用本地代码初始化所有的jfieldID/jmethodID.这样的话就可以省去多次的确定id是否存在的语句,当然,这些jfieldID/jmethodID是定义在C/C++的全局中。
使用这种方式 还有好处,当java类卸载或是重新加载的时候也会重新呼叫 该本地代码来重新计算IDs.
TestNative.java
1. 在本地代码中,创建String对象
jstring NewString(const jchar* str,jsize len); //传入一个宽字符串及长度,就能创建一个java的string对象
jstring NewStringUTF(const char* str); //传入一个UTF-8格式的字符串就可以
为什么不用传入字符串长度呢?C/C++中字符串都是以'/0'结尾的,通过这个‘/0’就能知道字符串的长度了。
jsize GetStringLength(jstring str);
jsize GetStringUTFLength(jstring str); //字符串以UTF-8格式存储时占用多少个字节。
代码示例:
MainTest.java
package cn.itcast;
public class MainTest{
public String message=null;
public native void callCppFun();
public static void main(String[] args) {
System.loadLibrary("JavaString");
/*用户输入一些字符串,在本地代码中访问这个字符串*/
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
String str=reader.readLine();
MainTest obj=new MainTest();
obj.callCppFun();
System.out.println("Java output:"+obj.message);
}
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
using namespace std;
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
/*得到MainTest类的message属性ID,这个message的类型是String*/
jfieldID fid_msg=env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String");
jstring j_msg=(jstring)env->GetObjectField(obj,fid_msg);/*这个j_msg就代表了java中的message*/
const jchar* jstr=env->GetStringChars(j_msg,NULL); /*将java中的字符串转换到本地的字符串*/
MessageBox(NULL,(const wchar_t*)jstr,L"Title",MB_OK); /*用对话框显示出来*/
wstring wstr((const wchar_t*)jstr); /*将jstr拷贝一下,成宽字符串*/
env->ReleaseStringChars(j_msg,jstr); /*释放掉空间,到这里就不需要本地字符串了*/
std::reverse(wstr.begin(),wstr.end()); //能够让容器中的内容倒序。
jstring j_new_str=env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size()); /*通过wstr得到一个新的java的字符串*/
env->SetObjectFile(obj,fid_msg,j_new_str); /*将字符串倒序后,设置回去*/
}
方法二:在本地创建一个字体数组
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
/*得到MainTest类的message属性,这个message的类型是String*/
jfieldID fid_msg=env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String");
jstring j_msg=(jstring)env->GetObjectField(obj,fid_msg);
jsize len=env->GetStringLength(j_msg);
jchar* jstr=new jchar[len+1];
jstr[len]=L'\0';
env->GetStringRegion(j_msg,0,len,jstr);/*将java的字符串拷贝到本地的字符数组中了*/
wstring wstr((const wchar_t*)jstr);
delete [] jstr;
std::reverse(wstr.begin(),wstr.end()); //能够让容器中的内容倒序。
jstring j_new_str=env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size());
env->SetObjectFile(obj,fid_msg,j_new_str); /*将字符串倒序后,设置回去*/
}
第6课 处理数组
数组分为两种:
1.基本类型的数组
2.对象类型(Object [])的数组
1. 一个能通用于两种不同类型数组的函数:
GetArrayLength(jarray array);
处理数组--基本类型数组1. 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_ABIRT ===》 对java的数组不进行更新,释放C/C++中的数组
GetPrimitiveArrayCritical(jarray arr,jboolean* isCopied);
ReleasePrimitiveArrayCritical(jarray arr,void* array,jint mode);
为了增加直接会加指向java数组的指针而加入的函数,同样的,也会有死锁的问题。
2.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,<TYPE>* buffer);
把java基本类型的数组中的指定范围的元素内 C/C++ 的数组中的元素来赋值。
3.<TYPE>Array New<TYPE>Array(jsize sz);
指定一个 长度然后返回相应java基本类型的数组。
处理数组--对象类型数组 (Object [ ])
1. JNI没有提供直接把Java的对象类型数组(Object[])直接转到C++中的jobject[]数组的函数。而是直接 通过Get/SetObjectArrayElement这样的函数来对Java的Object[]数组进行操作。
jobject GetObjectArrayElement(jobjectArray array,jsize index);
void SetObjectArrayElement(jobjectArray array,jsize index,jobject val);
使用以上函数不用释放 任何资源。
2. NewObjectArray 可以通过指定长度跟初始值来创建某个类的数组。
代码示例:在本地代码中访问java中的基本数组
TestNative.java
package cn.itcast;
public class MainTest{
public int[] arrays={1,2,3,4,5,6,9,2};
public native void callCppFun();
public static void main(String[] args) {
System.loadLibrary("JavaString");
MainTest obj=new MainTest();
obj.callCppFun();
for(int each:obj.arrays)
{
System.out.println(each);
}
}
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
#include <algorithm>
using namespace std;
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
jfieldID fid_arrays=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
jintArray jint_arr=(jintArray)env->GetObjectField(obj,fid_arrays);
jint* int_arr=env->GetIntArrayElements(jint_arr,NULL);
jsize len=env->GetArrayLength(jint_arr);
for(jsize i=0;i<len;++i)
{
cout<<int_arr[i]<<endl;
}
std::sort(int_arr,int_arr+len); //排序
env->ReleaseIntArrayElements(jint_arr,int_arr,JNI_ABORT);
}
在本地代码中成功访问了java中的数组。
第7课 全局引用/局部引用/弱全局引用
1. 从Java虚拟机创建的对象传到本地C/C++代码时会产生引用。根据java的垃圾回收机制,只要有引用存在就不会触发该引用指向的Java对象的垃圾回收。
这些引用在JNI中分为三种:全局引用(Global Reference)
局部引用(Local Reference)
弱全局引用(Weak Global Reference)
2. 局部引用
最常见的引用类型,基本上通过JNI返回来的引用都是局部引用。
例如使用 NewObject 就会返回创建出来的实例的局部引用。局部引用只在该 native 函数中有效,所有在该函数中产生的局部引用,都会在函数返回的时候自动释放(freed)。也可以用DeleteLocalRef函数手动释放该引用。
想一想既然局部引用能够在函数返回时自动释放,为什么还需要DeleteLocalRef函数呢?
实际上局部引用存在,就会防止其指向的对象被垃圾回收,尤其是当一个局部引用指向一个很庞大的对象 或是在一个循环中生成了局部引用,最好的做法就是在使用完该对象后,或在该循环尾部把这个引用释放掉,以确保在垃圾回收器被触发的时候被回收。
在局部引用的有效期中,可以传递到别的本地函数中,要强调的是他的有效期仍然只在一次的Java本地函数中,所以千万不能用C++全局变量保存他或是把他定义为C++静态局部变量。
3.全局引用
全局引用可以跨越当前线程,在多个native函数中有效,不过需要工程师手动来释放该引用。全局引用存在期间会防止在Java的垃圾回收的回收。
与局部引用不同,全局引用的创建不是由JNI自动创建的,全局引用是需要调用NewGlobalRef函数,而释放他需要使用ReleaseGlobalRef函数。
4.弱全局引用
与全局引用相似,创建跟删除都需要由工程师来进行,这种引用与全局引用一样可以在多个本地代码有效,也跨越多个线程有效。不同的是,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象。
使用NewWeakGloabalRef跟RelwaseWeakGlobalRef来产生和解除引用。
5.关于使用的一些函数:
jobject NewGlobalRef(jobject obj);
jobject NewGlobalRef(jobject obj);
void DeleteGlobalRef(jobject obj);
void DeleteLocalRef(jobject obj);
void DeleteWeakGlobalRef(jobject obj);
jboolean IsSameObject(jobject obj1, jobject obj2) ; //判断obj1,obj2是否指向同一个对象。
这个函数对于弱全局引用还有一个特殊的功能,把NULL传入要比较的对象中,就能够判断弱全局引用所指向的Java对象是否被回收。
6.缓存jfieldID/jmethodID
取得 jfieldID跟jmethodID的时候会通过该属性/方法名称加上签名来查询相应的jfieldID/jmethodID.
这种查询相对来说开销较大,我们可以将这些jfieldID/jmethodID缓存起来,这样只需要查询一次,以后就使用缓存起来的jfieldID/jmethodID,
下面介绍两种缓存方式:
1.在用的时候缓存
2.在java类初始化时缓存
1.在第一次使用的时候缓存
在native code中使用static局部变量来保存已经查询过的id.这样就不会在每次的函数调用时查询.
不过这种情况下就不得不考虑多线程同时呼叫此函数时可能会招致同时查询的危机.
JNIEXPORT void JNICALL ava_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
static jfieldID fid_string=NULL;
jclass clazz=env->GetObjectClass(obj);
if(fid_string==NULL)
{
fid_string=env->GetFieldID(clazz,"string","Ljava/lang/String");
}
........
}
2.在java类初始化的时候缓存
更好的一个方式就是在任何native函数调用前把id全部存在来.
我们可以让java在第一次加载这个类的时候首先调用本地代码初始化所有的jfieldID/jmethodID.这样的话就可以省去多次的确定id是否存在的语句,当然,这些jfieldID/jmethodID是定义在C/C++的全局中。
使用这种方式 还有好处,当java类卸载或是重新加载的时候也会重新呼叫 该本地代码来重新计算IDs.
TestNative.java
package cn.itcast;
public class TestNative
{
static{
initNativeIDs();
}
public native void initNativeIDs();
int propInt=0;
String propStr="";
public native void otherNative();
.............
}
source.cpp
jfieldID g_propInt_id=0;
jfieldID g_propStr_id=0;
JNIEXPORT void JNICALL Java_cn_itcast_testNative_callCppFun(JNIEnv *env,jobject obj)
{
g_propInt_id=GetFieldID(obj,"propInt","I");
g_propStr_id=GetFieldID(obj,"propStr","Ljava/lang/String");
}