1. 对象成员变量的访问
函数原型:
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
代码实例:
JNIEXPORT void JNICALL Java_com_fyj_test_InstanceFieldAccess_accessField
(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jstring jstr;
const char *str;
/* get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* look for the instance field s in cls */
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
if (fid == NULL)
return;
/* Read the instance fields s */
jstr = (*env)->GetObjectField(env, obj, fid);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL)
return;
printf(" s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* create a new string and overwrite the instance field */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL)
return;
(*env)->SetObjectField(env, obj, fid, jstr);
}
1. 类成员变量的访问
函数原型
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
代码示例:
JNIEXPORT void JNICALL Java_com_fyj_test_StaticFieldAccess_accessField
(JNIEnv *env, jobject obj)
{
jfieldID fid;
jint si;
jclass cls = env->GetObjectClass(obj);
cout<<"In C++:\n";
fid = env->GetStaticFieldID(cls, "si", "I");
if (fid == NULL)
return;
si = env->GetStaticIntField(cls, fid);
cout<<" StaticFieldAccess.si = "<<si<<endl;
env->SetStaticIntField(cls, fid, 200);
}
通过上述例子可知,在JNI层访问Java类的成员变量分为两步:
1. 通过调用GetFieldID或GetFieldID获得成员变量相应的ID实例。
2. 然后根据ID来获得对成员变量的引用。
3. 方法调用
也包含实例方法的调用和类的方法调用。
步骤也是分为两步:
1. 通过调用GetMethodID或GetStaticMethodID获得方法ID。
2. 通过ID调用CallVoidMethod或CallStaticVoidMethod调用Java类的方法。
原型声明:
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
<NativeType> CallStatic<Type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
代码示例:
JNIEXPORT void JNICALL Java_com_fyj_test_InstanceMethodCall_nativeMethod
(JNIEnv *env, jobject obj)
{
jclass cls = env->GetObjectClass(obj);
jmethodID mid = env->GetMethodID(cls, "callback", "()V");
if (mid == NULL)
return;
cout<<"In C++ "<<endl;
env->CallVoidMethod(obj, mid);
4.调用构造方法
要获得构造方法的调用,需要传入“<init>”作为方法名,”V”作为方法返回类型描述,然后通过调用NewObject函数调用。
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
代码示例如下:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;
stringClass = env->FindClass("java/lang/String");
if (stringClass == NULL)
return NULL;
cid = env->GetMethodID(stringClass, "<init>", "([C)V");
if (cid == NULL)
return NULL;
elemArr = env->NewCharArray(len);
if (elemArr == NULL)
return NULL;
env->SetCharArrayRegion(elemArr, 0, len, chars);
result = (jstring)env->NewObject(stringClass, cid, elemArr);
//free local references
env->DeleteLocalRef(elemArr);
env->DeleteLocalRef(stringClass);
return result;
}
局部引用和全局引用
JNI支持三种类型的引用:局部引用,全局引用以及弱全局引用。
局部引用是自动被释放的,而全局引用和弱全局引用则是需要程序员显式释放的。
局部或全局引用阻止被引用的对象回收,而弱全局引用,则允许被引用的对象回收。
局部引用
大部分JNI函数产生局部引用。通过调用DeleteLocalRef释放局部引用对象。
全局引用
全局引用由唯一的一个JNI函数创建:NewGlobalRef。
创建
jobject NewGlobalRef(JNIEnv *env, jobject obj);
释放
void DeleteGlobalRef(JNIEnv *env, jobject gref);
弱全局引用
创建弱全局引用函数:NewWeakGlobalRef
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
释放弱全局引用函数:DeleteGlobalWeakRef
void DeleteWeakGlobalRef(JNIEnv *env, jobject wref);
引用比较
使用IsSameObject来比较两个引用对象是否指向同一个对象。
管理局部引用
JNI文档上规定虚拟机会自动确保每个本地方法可以创建至少16个局部引用。如果16个不够,则可以通过调用EnsureLocalCapacity来扩大允许创建的局部引用变量的个数。
另外,Push/PopLocalFrame函数允许程序员在一个嵌套的区域创建局部引用。
jint PushLocalFrame(JNIEnv *env, jint capacity);
jobject PopLocalFrame(JNIEnv *env, jobject result);
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
JNI 异常处理
这里的异常主要是指调用JNI函数产生的异常。先看一个代码实例:
JNIEXPORT void JNICALL Java_com_fyj_test_CatchThrow_doit
(JNIEnv *env, jobject obj)
{
jthrowable exc;
jclass cls = env->GetObjectClass(obj);
jmethodID mid =
env->GetMethodID(cls, "callback", "()V");
if (mid == NULL) {
return;
}
env->CallVoidMethod(obj, mid);
exc = env->ExceptionOccurred();
if (exc) {
/* We don't do much with the exception, except that
we print a debug message for it, clear it, and
throw a new exception. */
jclass newExcCls;
env->ExceptionDescribe();
env->ExceptionClear();
newExcCls = env->FindClass(
"java/lang/IllegalArgumentException");
if (newExcCls == NULL) {
/* Unable to find the exception class, give up. */
return;
}
env->ThrowNew(newExcCls, "thrown from C code");
}
}
如上代码所示:
判断JNI函数调用是否产生异常用如下函数:
jthrowable ExceptionOccurred(JNIEnv *env);
returns a local reference to the pending exception, or returns NULL if there is no pending excep-
tion.
jboolean ExceptionCheck(JNIEnv *env);
返回JINI_TRUE表示发生了异常,否则无异常。
打印异常信息用如下函数:
void ExceptionDescribe(JNIEnv *env);
清除异常使用如下函数:
void ExceptionClear(JNIEnv *env);
抛出异常使用如下函数:
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
Java虚拟机调用接口
有两个重要的数据结果对象:JavaVM和JEnv,其中JavaVM是在所有线程中都有效,而JEnv则只在当前线程中有效。
创建虚拟机
函数原型
jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *vm_args);
对于Java 2 SDK,
JavaVMInitArgs vm_args;
JavaVMOption options[4];
/* disable JIT */
options[0].optionString =
"-Djava.compiler=NONE";
/* user classes */
options[1].optionString =
"-Djava.class.path=c:\\myclasses";
/* native lib path */
options[2].optionString =
"-Djava.library.path=c:\\mylibs";
/* print JNI msgs */
options[3].optionString = "-verbose:jni";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
res = JNI_CreateJavaVM(&vm, (void **)&env,
&vm_args);
if (res < 0) {
... /* error occurred
}
其中相关的数据结果定义如下:
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
typedef struct JavaVMOption {
char *optionString;
void *extraInfo;
} JavaVMOption;
关联本地线程
当在Native代码中访问JNI的接口(此线程中尚未创建JEnv对象),必须调用如下的接口:
jint AttachCurrentThread(JavaVM *vm, void **penv, void *args);
通过上述调用,前当前线程关联进当前虚拟机中,取得了当前线程的JEnv对象的指针,这样,在当前线程中就可以调用JNI接口了。通过如下函数取消关联:
jint DetachCurrentThread(JavaVM *vm);
在任何时候,可以通过如下方式获得当前线程的JEnv对象:
JavaVM *jvm; /* already set */
f()
{
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
... /* use env */
}
JNI线程
JEnv对象的引用只在当前线程有效,不能从一个线程传递到另一个线程。本地引用对象只能在当前线程中访问,不能传递到另一个线程中。
在JNI层,可以通过如下接口实现同步访问一段代码:
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
... /* error handling */
}
...
/* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
... /* error handling */
};
国际化支持
Java虚拟机用Unicode的格式表示字符串,而大多数本地平台使用本地语言相关的字符串编码格式表示字符串,所以,在两者之间进行转换时,不能使用GetStringUTFChars和GetStringUTFRegion。而应当使用String(byte[] bytes)将本地字符串转换为jstring, 使用String.getBytes方法将jstring转换为本地字符串。
注册本地方法
通常在Java类中调用执行本地方法,需要经历如下两步:
1. 通过System.loadLibrary定位和加载指定的库。
2. Java虚拟机在加载的本地库中定位对应的本地方法,如Foo.g本地方法调用将会使虚拟机查看和链接位于库的本地函数:Java_Foo_g(注:本地方法的命名必须符合JNI函数命令规范)
对于第二步,可以使用另一种方式,即通过如下方式:
static JNINativeMethod nms[] = {
{“g”, “()v”, (void*)&g_impl},
};
(*env)->RegisterNatives(env, cls, nms, sizeof(nms)/sizeof(nms[0]));
通过上述方式,不再依赖虚拟机在已经加载的库中搜索要调用的本地方法。
装载和卸载处理器
当调用System.loadLibary加载本地库时,虚拟机会调用装载处理器函数:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
通过这个接口,可以获得指向当前JavaVM虚拟机实例,既而获得当前线程的JavaEnv实例:
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
return JNI_ERR; /* JNI version not supported */
}
当虚拟机卸载本地库时,也会调用相应的卸载处理器函数:
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved);
Peer类
在Java类的设计中,通常会定义一个32bit的int或64bit的long私有成员变量来代表一个对应的Peer类,它主要是给Native层访问的,在Java层,它就是一个普通的int或long型的数据,而在JNI层,它则是指向一个本地数据结构的地址。这个本地数据结构要么本身就是或者内部包含一个指向Java类相对应的本地类(Peer类)的变量。这样,通过Java本地接口的调用,会将调用传递给Peer类的对应接口。
这种设计方法通常出现在为C++类定义相对应的Java类的时候,C++类就是Java类对应的Peer类。如android中的Parcel类。
反之,亦然。