1.JNI和线程:
Java 虚拟器支持控制并发的在一样地址空间中执行的多线程,多线程可以访问同一个对象,同一个文件描述符。
多线程的限制:
- 一个"JNIEnv"指针只在和其关联的线程中有效。不必传递这个指针从一个线程到另一个线程, 或者在多线程中缓冲和使用它。在从同一个线程的并发调用中,"Java"虚拟器传递给一个本地方法同样的"JNIEnv"指针;但当从不同线程中调用那个本地方法时,传递不同的"JNIEnv"指针。 避免在一个线程缓冲"JNIEnv"指针和在另一线程中使用这个指针的一般性错误。
- 局部引用只在创建它们的线程中是有效的。不必传递局部引用从一个线程到另一个线程。应该总是转换局部引用为全局引用,任何时候多线程可以使用一样的。
监视入口和监视出口:
Java中的同步机制,每个对象能被动态地关联到一个监视上。Java虚拟器保证了在线程执行块中的任何语句前,线程获得和“obj”对象关联的监视。这确保在给定的任何时候在这至多有一个线程拥有监视器和在同步块中执行,当一个线程等待另一个线程退出监视时,这线程阻塞:
synchronized(obj){
// synchronized block
}
JNI中本地代码使用JNI函数来实现同步,使用"MonitorEnter"函数来进入监视和"MonitorExit"函数来退出监视,调用"Mon itorExit "的失败将几乎可能导致死锁,所以在次最好作出异常处理:
if((*env)->MonitorEnter(env, obj) != JNI_OK);
if ((*env)->ExceptionOccurred(env)){
if ((*env)->MonitorExit(env, obj) != JNI_OK);
}
监视的等待和通知:
在Java的提供"Object.wait","Object.notify","Object.notifyAll"的同步相关的方法;而JNI没有提供类似的方法,但可以本地方法调用Java来去实现:
static jmethodID MID_Object_wait ;
static jmehtodID MID_Object_notify ;
static jmethodID MID_Object_notifyAll ;
void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout) {
(*env)->CallLongMethod(env, object, MID_Object_wait, timeout) ;
}
void JNU_MonitorNotify(JNIEnv *env, jobject object) {
(*env)->CallVoidMethod(env, object, MID_Object_notify) ;
}
void JNU_MonitorNotifyAll(JNIEnv *env, jobject object) {
(*env)->CallVoidMethod(env, object, MID_Object_notifyAll) ;
}
在任意上下文获取一个指针“*JNIEnv”:
"JNIEnv"指针只在它关联的线程中有效,当不能从虚拟器被直接调用来得到属于当前线程的"JNIEnv"接口指针。通过"AttachCurrentThread"函数的接口调用来获取:
获取Java VM指针的方法:
- 通过在创建虚拟器的时候记录;
- 通过使用"JNI_GetCreateJavaVMs"来查询被创建的虚拟器;
- 通过在一个一般的本地方法中调用"JNI"函数"GetJavaVM";
- 或者通过定义一个"JNI_OnLoad"处理。
JavaVM *jvm
f() {
JNIEnv *env ;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL ) ;
}
匹配线程模型:
如果"Java"虚拟器实现支持一种线程模型,且线程模型匹配了通过本地代码的虚拟器,那 时所有这些方法(approach)都能工作。线程模型(thread model)指示(dictat)系统怎样实现必要的线 程操作,例如时序安排(scheduling),上下文的切换(context switching),同步(sychronization),和 在系统调用中的阻塞(blocking)。另一方面,在一个用户的线程模型中,应用程序代码实现了线程的操作。
2.字符串string的相关操作:
- 从本地字符串创建"jstring":
使用"string(byte[] bytes)"构造器(constructor)来转换一个本地字符串为一个"jstring",MID_String_init是字符构造器的方法ID。
jstring JNU_NewStringNative(JNIEnv *env, const char *str)
{
jstring result ;
jbyteArray bytes = 0 ;
int len ;
if((*env)->EnsureLocalCapacity(env, 2)< 0 ){
return NULL ;
}
len = strlen(str) ;
bytes = (*env)->NewByteArray(env, len) ;
if (bytes != NULL ){
(*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *)str) ;
result = (*env)->NewObject(env, Classjava_lang_String,
MID_String_init, bytes) ;
(*env)->DeleteLocalRef(env, bytes) ;
return result ;
}
}
- 转换jstring到本地字符串:
使用"String.getBytes"方法来转换一个"jstring"为恰当的本地编码。
char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
jbyteArray bytes = 0 ;
jthrowable exc ;
char *result = 0 ;
if( (*env)->EnsureLocalCapacity(env, 2) < 0 ){
return 0 ;
}
bytes = (*env)->CallObjectMethod(env, jstr, MID_String_getBytes) ;
exc = (*env)->ExceptionOcurrend(env) ;
if(!exc){
jint len = (*env)->GetArrayLength(env, bytes) ;
result = (char *)malloc(len+1) ;
if (result == 0 ){
JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0 ) ;
(*env)->DeleteLocalRef(env, bytes) ;
return 0 ;
}
(*env)->GetByteArrayRegion(env, bytes, 0, len, (jbyte *)result) ;
result[len] = 0 ;
} else{
(*env)->DeleteLocalRef(env, exc) ;
}
(*env)->DeleteLocalRef(env, bytes) ;
return result ;
}
3.注册本地方法:
"JNI"编程者能手动地(manually )链接本地函数,通过注册带有一个类引用,方法名字和方法描 述符的一个函数指针。
JNINativeMethod nm;
nm.name = "g";
nm.signature = "()V" ;
nm.fnPtr = g_impl ;
(*env)->RegisterNatives(env, cls, *nm, 1) ;
//代码注册本地函数"g_impl"做为"Foo.g"本地方法的实现
void JNICALL g_impl(JNIEnv *env, jobject self) ;
4.载入和载出处理程序:
载入和载出处理程序允许本地库导出两个函数:一个在 "System.loadLibrary"载入本地库时调用,另一个在虚拟器载出本地库时调用。
- JNI_OnLoad 处理程序:
当"System.loadLibrary"载入一个本地库时,虚拟器在本地库中搜索下面导出入口
JNIEXPORT jint JINCALL JNI_OnLoad(JavaVM *jvm, void *reserved) ;
在JNI_OnLoad的实现中,能调用任何"JNI"函数。"JNI_OnLoad "处理程序的典型使用时缓冲"JavaVM"指针,类的引用,或者方法和域的"IDs"
JavaVM *cached_jvm ;
jclass Class_C ;
jmethodID MID_C_g ;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv *env ;
jclass cls ;
cached_jvm = jvm ;
if((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)){
return JNI_ERR ;
}
cls = (*env)->FindClass(env, "C") ;
if( cls == NULL ){
return JNI_ERR ;
}
Class_C = (*env)->NewWeakGlobalRef(env, cls) ;
if(Class_C == NULL ){
return JNI_ERR ;
}
MID_C_g = (*env)->GetMethodID(env, cls, "g","()V") ;
if(MID_C_g == NULL ){
return JNI_ERR ;
}
return JNI_VERSION_1_2 ;
}
- JNI_OnUnload 处理程序:
载出本地库的规则是如下:
虚拟器关联每个本地库使用"class C"的类载入器"L"调用了"System.loadLibrary"函数;
在它决定类载入器"L"不在是一个活的对象(a live object)后,虚拟器调用"JNI_OnUnload"处理, 同时载出本地库。因为一个类载入器查看了这个虚拟器定义的所有的类,这暗示(imply)类 C 也 能被载出;
"JNI_OnUn load "处理程序在最后运行,且被 "java.lang.System.runFinalization"同步地调用或者被 虚拟器同步地调用。
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved)
{
JNIEnv *env ;
if((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)){
return ;
}
(*env)->DeleteWeakGlobalRef(env, Class_C) ;
return ;
}
- 反射支持(Reflection Support):
反馈允许你在运行(at run time)时发现任何类对象,系列域和在类中定义的方法的名字。在 Java 编程语言级通过"java.lang.reflect"包来提供反射的支持,和在"java. lang.Object"和"java.lang.Class"类中的一些方法一样。虽然你总能调用对应的"Java API"来执行(carry out)反馈操作,"JNI"提供下面函数使来自本地代码的频繁的反馈操作更有效和方便:
- "GetSuperclass"返回一个被给类引用的父类;
- "IsAssignableFrom"检查一个类的实体是否能被用,当另一个类的事例期待使用时;
- "GetObjectClass"返回被给"jobject"引用的类;
- "IsInstanceOf "检查一个"jobject"对象是否是一个被给类的实体;
- "FromReflectedField and ToReflectedField"允许本地代码在域"ID"和"java.lang.reflect.Field"对象之间转换;
- "FromReflectedMethod and ToReflectedMethod"允许本地代码在方法"IDs","java.lang.reflect.Method objects"和"java.lang.reflect.Constructor objects"之间转换。
- JNI 在 C++中的编程:
jclass cls = env->FindClass("java/lang/String");
//C中的调用在用以上C++的代替:
jclass cls = (*env)->FindClass(env, "java/lang/String");
"jni.h"文件也定义一些列空的"C++"类来强制在不同的"jobject"子类型中子类化联系:
// JNI reference type defined in C++
class _jobject{} ;
class _jclass: public _jobject{} ;
class _jstring: public _jobject{} ;
typedef _jobject * jobject ;
typedef _jclass* jclass ;
typedef _jstring* jstring ;
在 C++中加入类型的层次(type hirrarchy)有时额外的转换(casting)成必要(necessitate):
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i) ;
//在"C++"中你需要插入一个清晰的转换
jstring jstr = (jstring)env->GetObjectArrayElement(arr, i) ;
5.利用存在的本地库:
- 一对一映射(Java的方法对应JNI唯一的函数):
一对一的映射方法(approach)需要你来写一个存根函数为打包的每个本地函数。
在一对一映射 ,存根的(stub)函数有两个目的:
- 存根函数使本地函数的参数传递的协定适合"Java"虚拟器的期望。虚拟器期望本地方法实现为 一个被给地命名协定和接受俩个额外的参数("JNIEnv"指针和"this"指针);
- 存根函数在"Java"编程语言类型和本地类型之间转换。例如,"Java_Win32_CreateFile"函数转换"js tring"文件名为一个本地描述的 "C "字符串。
- 共享存根(把一系列的本地方法调用实现封装在Java的类中):
一个共享存根是一个本地函数,这函数分配给其他本地函数。共享存根负责转换来自调用者提供的参数类型到本地函数能够接受的参数类型。
- 一对一映射与共享存根相对:
一对一的映射和共享存根是两种为本地库建立封装类的方法,每个方法有它自己优点。共享存根的主要优点是程序员不需要写在本地代码中写大量的存根函数;一对一映射的优点是典型地在转换数据类型中更有效率,数据是在"Java"虚拟器和本地代码(native code)之间传递。
- 共享存根的实现:
6.陷阱和缺陷:
- 检查错误:
"JNI"不能依赖任何特殊的本地异常机制(例如"C++"异常)。 因此,在每个可能产生一个异常的"JNI"函数调用后 ,编程者被要求执行清楚地检查;
- 传送无效参数给"JNI"函数:
"JNI"函数不会尝试检查(detect)和恢复(recorer from)无效参数;
- 混淆"jclass"和"jobject":
实例的引用对应"java.lang.Object"或它的子类的一种的实例和数组。类引用对应"java.lang.Class"实例,代表类的类型(class types);
- 截短"jboolean"参数:
一个"jboolean"是一个"8-bit"无符号"C"类型,它能存储 0 到 255 的值。0 值对应常数"JNI_FALSE",1到 255 的值对应"JNI_TRUE"。但"32-bit or 16-bit"大于 255 的值,它的低"8 bits"是 0 时,造成(pose)一个问题;
- "Java"应用和本地代码之间的边界线:
保持边界简单;在本地代码方保持代码尽力少;保持本地代码独立;
- 混淆 IDs 和引用:
"JNI"揭示对象(objects)为引用。类,字符串和数组(as references, Classes, strings, and arrays)是引用的特别类型。"JNI"方法和成员域为 IDs。一个 ID 不是一个引用。不能认为一个类的引用(class reference)是一个"Class ID",或者一个方法 ID 是一个"方法应用(method reference)"。
- 缓冲成员域和方法 IDs:
本地代码通过指定的成员域或方法的名字和类型描述符作为字符串,从虚拟器得到成员域或方法"IDs"。成员域和方法查看使用的名字和类型字符串是很慢的。通常为解决这问题来缓冲"IDs"。在本地代码中,缓冲成员域和方法 ID 的失败是一种常见的性能问题。
- Unicode 字符串的结束:
-
从"GetStringChars or GetStringCritical"得到 Unicode 字符串是没有"NULL"结束的(NULL-terminated)。调用"GetStringLength"来发现"16-bit Unicode"字符个数在字符串中。
- 违反访问控制规则:
"JNI"不能强制"c lass, field, and method"访问控制限制,限制就是在"Java"编程语言层通过修饰符 的使用例如"private"和 "f inal"来表示的 。写本地代码访问或修改一个对象的成员域是可能的,即使在"Java"编程语言层如此做将导致一个"IllagalAccessException"。
- 漠视国际化:
- 保留虚拟器资源:
-
在本地方法中一个通常的错误是忘记释放虚拟器的资源。
- 过度的局部引用创建:
过度局部引用的创建导致程序不必要的保留内存。一个不必要的局部引用浪费为被引用对象的
内存和为引用自己的内存。
- 使用无效的局部引用:
-
局部引用只在一个本地方法的单次调用(invocation)中是有效的。当实现方法的本地函数返回后, 在一个本地方法调用中创建的局部引用自动被释放。
- 在线程间使用"JNIEnv":
-
"JNIEnv"指针,作为第一个参数传递给每一个本地方法,只能在和它关联的线程中被使用。从 一个线程得到缓冲的"JNIEnv"接口指针,同时在另一个线程中使用这个指针,是错误的。
- 不匹配的线程模式:
-
只有主机的本地代码和"Java"虚拟器实现共享一样线程的模式,JNI才能运行。
-