1.Java平台环境简介:
"Java"平台的编程环境包含"Java"虚拟机(VM)和 Java 应用程序编程的接口(Java Application Programming Interface(API))。"Java"应用程序是用"Java"编程语言编写的,被编译成一个独立于 机器(machine-independent)二进制类格式.一个类在任何 Java 虚拟机上执行实现。Java 的 API 包 含预定义的类集合。Java"平台的任何实现被假设支持 Java 编程语言,虚拟机和"API"。
主机环境(host environment)术语代表主机操作系统,本地库组,和 CPU 指令集。用本地变成语 言(native programming languages)如"C"和"C++"编写本地应用程序(Native application),被编译特定主机的二进制编码,和被连接到本地库。本地应用程序和本地库是典型依赖于特定主机环境, 为一个操作系统建立的一个"C"应用程序。
"Java"平台被一般的配置在主机环境的上面。例如,"JRE"(Java Runtime Enviroment)是"Sun"产品, 它支持"Java"平台运行在例如"Solar is"和"Windows"的存在操作系统上。"Java"平台提供一组特性,应用程序能依赖独立于底下的主机环境。
JNI 的一个强大的特性是允许你来利用"Java"平台,但利用的代码仍然用其他语言来编写。作为"Java"虚拟机(Jave virtual machine)执行的一部分,"JNI"是一个双向接口,允许 Java 应用程序调用本地代码,反之亦然。
"JNI"被设计为处理你需要联合"Java"应用程序和本地代码的情况。做为一个双向接口,"JNI"能支持两种本地编码:本地库(native libraries)和本地应用程序(native application)。.你能使用"JNI"来编写,允许 Java 应用程序来调用在本地库中实现的函数的本地方法(native method)。"Java"应用程序,通过和他们调用在 Java 编程语言中实现的方法一样办法,调用本地 方法。然而幕后,本地方法是用另一种语言实现的和位于本地库中。
2.JNI相关概要/基本类型/字符串/数组:
对于每个本地方法的实现的第一 个参数都是一个"JNIEnv"接口的指针第二个参数是对象自身的一个参考(在"C++"
中就像"this"指针)。
JNI的实质:就是对于程序来说需要传递参数给本地方法,和从本地方法中获取结果。
- 类型的映射:在本地方法声明中参数类型有对应本地编程语言中类型,JNI定义了一套C/C++类型来对应Java编程语言的类型,JNI传递objects到本地方法作为不透明的引用,不透明引用是一个C类型的指针,引用了在Java虚拟机内部的数据结构。Java函数对JNIEnv接口指针式可用的,所有的JNI引用类型都是jobject;
- 访问String:通过GetStringUTFChars()等类型转换函数把Java中string类型映射成C/C++中的string类型,在不用字串要对其释放如ReleaseStringUTFChars()函数。
Java_Prompt_getLine(JNIEvn *env, jobjext obj, jstring prompt) {
char buf[128] ;
const jbyte *str ;
str = (*env)->GetStringUTFChars(env, prompt , NULL) ;
if (NULL == str) {
return NULL;
}
printf("%s", str) ; (*env)->ReleaseStringUTFChars(env, prompt, str) ;
scanf("%s", buf) ;
return (*env)->NewStringUTF(env, buf) ;
}
- JNI抛出异常:JNI抛出异常不同于Java语言中抛出的异常,通过JNI抛出一个未决异常不自动改变在C/C++代码中的控制流。替代是发布一个清楚的返回声明来在C/C++函数中跳过剩余的语句。
- 访问数组:数组通过jarray引用类型表示,Get/ReleaseIntArrayElements函数允许本地代码来得到一个直接指向基本类型数组元素的指针。因为底层的垃圾回收器可能不支持固定,虚拟器可能返回一个指向原始基本类型数组的一个副本。
Java_IntArray_sumArray(JNIEnv *env, jobjext obj, jintArray arr)
{
jint *carr ;
jint i, sum =0 ;
carr = (*env)->GetIntArrayElements(env, arr, NULL ) ;
if (carr == NULL){
return 0 ;
}
for (i = 0 ; i < 10 ; i++ ){
sum +=carr[i] ;
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0) ;
return sum ;
}
- 访问Object类型的数组:"JNI"提供单独一对函数来访问"Objects"类型数组。"GetObjectArrayElement"函数返回被给的一个索引的元素,然而"SetOb jec tArrayElement "函数更新被给的一个索引的元素。不像基本类型数组情况,你不能立马得到所有"object "类型的元素或复制多个"object"类型的元素。
Java_ObjextArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
{
jobjectArray result;
int i ;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL){
return NULL ;
}
result = (*env)->NewObjectArray(env, size, intArrCls, NULL) ;
if (result == NULL ){
return NULL ;
}
for( i = 0 ; i < size ; i++){
jint tmp[256] ;
jint j ;
jintArray iarr = (*env)->NewIntArray(env, size) ;
if ( iarr == NULL){
return NULL ;
}
for (j = 0 ; j < size ; j++ ){
tmp[j] = i + j ;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp) ;
(*env)->SetObjectArrayElement(env, result, i, iarr) ;
(*env)->DeleteLocalRef(env, iarr) ;
}
return result ;
}
3.成员和方法(Fields and Methods):
Java语言中支持两种类型的成员,实例成员域和静态成员域。
JNI提供函数,本地代码能使用用它来得到和设置在对象(objects)中的实例成员域和在类中的静态成员域。
- 访问成员域:一旦得到成员域的ID,就能传递对象引用和成员域给相应的实例域访问函数:
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid ;
jstring jstr ;
const char *str ;
jclass cl = (*env)->GetObjectClass(env, obj) ;
printf("In C:\n") ;
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;") ;
if( fid == NULL ){
return ;
}
jstr = (*env)->GetObjectField(env, obj, fid) ;
str = (*env)->GetStringUTFChars(env, jstr, NULL) ;
if( str == NULL ){
return ;
}
printf(" c.s = "%s"\n", str ) ;
(*env)->ReleaseStringUTFChars(env, jstr, str) ;
jstr = (*env)->NewStringUTF(env, "123") ;
if( jstr == NULL ){
return ;
}
(*env)->SetObjectField(env, obj, fid, jstr) ;
}
- 成员域的描述符:一个类型引用的描述符如Java中的"java.lang.String"的成员域描述符在JNI中使用为"Ljava/lang/String"。对于数组的的描述符包含字符"[",被数组的组成类型的描述符跟着如"[I"是"int[]"的成员域类型的描述。
- 访问静态成员域:使用GetStaticFieldID()函数,一旦得到静态成员域ID相对于一个对象引用来给适当静态成员域访问函数。
- 访问方法:"JNI"支持一整套函数来允许你执行来自本地代码的回调。"JNI"是用的构造描述符字串来表示方法类型,如Java中的private String getString(String s)对应的是红色为返回类型:"(Ljava/ lang/String;) Ljava/ lang/String; "
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj) ;
jmethod mid = (*env)->GetMethodID(env, cls, "callback", "()V") ;
if(mid == NULL){
return;
}
printf("In C\n") ;
(*env)->CallVoidMethod(env, obj, mid) ;
}
- 调用静态方法:用GetStaticMethodID()函数,在允许你调用的静态方法的函数和允许你调用实例方法的函数之间有一个关键的不同点。前者(former)调用一个类的引用(class reference)作为第二个参数,然而后者(latter)使用一个对象应用(object reference)作为第二个参数。
- 调用一个超类的实例方法:
- 调用构造器:可以使用"Ca llNonv irtua lVo idMethod "函数来调用构造器。
- 缓冲成员域和方法ID:得到成员域和方法"ID"需要基于名字和成员域或方法的描述符的符号的查找。符号的查找是相对比较是费时的。
使用时缓冲:成员域和方法 ID 可以被缓冲,在本地代码访问成员域值或执行方法回调的地方:
Java_InstanceFieldAccess_accessField(JNIEnv *env,jobject obj)
{
static jfieldID fid_s = NULL ;
jstring jstr ;
const char *str ;
if (fid_s == NULL ){
fid_s = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;") ;
if( fid_s == NULL) {
return ;
}
}
printf("In C:\n") ;
jstr = (*env)->GetObjectField(env, obj, fid_s) ;
str = (*env)->GetStringUTFChar(env, jstr, NULL) ;
if ( str == NULL ){
return ;
}
printf(" c.s = "%s"\n", str) ;
(*env)->ReleaseStringUTFChars(env, jstr, str) ;
jstr = (*env)->NewStringUTF(env, "123") ;
if( jstr == NULL){
return ;
}
(*env)->SetObjectField(env, obj, fid_s, jstr) ;
}
在定义类的初始化中缓冲:引入本地方法“initIDs”,在Java类静态初始化中调用。
Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) {
MID_InstanceMethodCall_callback = (*env)->GetMethodID(env, cls, "callback", "()V") ;
}
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
printf("In C\n") ;
(*env)->CallVoidMethod(env, obj, MID_InstanceMethodCall_callback) ;
}
4.局部和全局引用:
"JNI"支持三种类型不透明引用:局部引用,全局引用和弱全局引用。
局部和全局引用有不同的生命周期(lifet imes)。局部引用会自动释放,然而全局和弱全局引用保持有效一直到它们被程序员释放。
一个局部或全局的引用保持了引用的对象(referenced object)不被垃圾收集掉。另一方面,一个弱全局引用允许引用的对象被垃圾收集。
- 局部引用:一个局部引用,只在创建它的本地方法的动态上下文中,同时只在本地方法的一个调用中是有效的。所有的在一个本地方法执行期间创建的局部引用将被释放,一旦本地方法返回;
- 全局引用:一个全局引用能在多个线程中(across muliple threads)被使用,同时保持有效直到编程者释放它。像一个局部引用,一个全局引用确保 了引用对象将不被垃圾收集。全局引用只被一个JNI函数"NewGlobalRef"创建。
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
static jclass stringClass = NULL ;
if ( stringClass == NULL ){
jclass LocalRefCls = (*env)->FindClass(env, "java/lang/String") ;
if (localRefCls == NULL ){
return NULL ;
}
stringClass = (*env)->NewGlobalRef(env, localRefCls) ;
(*env)->DeleteLocalRef(env, localRefClas) ;
if( stringclass == NULL ){
return NULL ;
}
}
}
- 弱全局引用:使用"NewGlobalWeakRef"来创建和使 用"DeleteGlobalWeakRef"来释放,弱全局引用不能保持底层对象不被垃圾收集。
- 引用比较:使用"IsSameObject"函数,一个在"JNI"中的"NULL"引用参考"null"对象,对于弱引用只能参考 "nu ll"对象。
(*env)->IsSameObject(env, obj1, obj2);
(*env)->IsSameObject(env, wobj, NULL);
- 释放引用:
释放局部引用:一般垃圾回收器会自动回收,但有时创建太多时也要自己去释放,"JNI"说明指示虚拟器自动地确保每个本地方法能创建至少 16 个局部引用。
(*env)->DeleteLocalRef(env, jstr) ;
释放全局和弱引用:当你的本地代码不再需要访问全局引用时,你应该调用"DeleteGlobalRef"。如果你调用这个函数失败,"Java"虚拟器将不能垃圾收集这对应的对象,即使在系统的任何地方当对象不再使用的时候。
当你的本地代码不再需要访问一个弱全局引用,你应该调用"DeleteWeakGlobalRef"。如果你调 用这个函数失败,"Java"虚拟器任然将能垃圾收回底层对象,但将不能收回(reclaim)被弱全局应 用自己消耗的内存。
5.异常:
- 在本地方法中缓冲和抛出异常:"CatchThrow "类声明一个"doit"本地方 法,同时指定他抛出一个"IllegalArgumentException"。
Java_CatchThrow_doit(JNIEnv *env, jobject obj)
{
jthrowable exc ;
jclass cls = (*env)->GetObjectClass(env, obj) ;
jmethodID mid =
(*env)->GetMethodID(env, cls, "callback", "()V") ;
if ( mid == NULL ){
return ;
}
(*env)->CallVoidMethod(env, obj, mid) ;
exc = (*env)->ExceptionOccurred(env) ;
if (exc){
jclass newExcCls ;
(*env)->ExceptionDescribe(env) ;
(*env)->ExceptionClear(env) ;
newExcCls = (*env)->FindClass(env,
"java/lang/Illega lArgument Exception ");
if( newExcCls == NULL ){
return ;
}
(*env)->ThrowNew(env, newExCls, "thrown from C code") ;
}
}
- 检查异常:有两种方法检查是否有错误发生。
大多"JNI"函数使用一个清晰的(distinct)返回值(例如 NULL)来指示一个错误发生。错误返回值也暗示在当前线程有个未解决的异常;
当使用一个"JNI"函数的返回值不能标记一个错误发生时,本地代码必须依赖产生异常来做错 误检查。在当前线程中执行一个未决异常检测的"JNI"函数是"ExceptionOccurred"。
void f(JNIEnv *env, jobject fraction) {
jint floor = (*env)->CallIntMethoed(env, fraction, MID_Fraction_floor) ;
if((*env)->ExceptionCheck(env)){
return ;
}
}
- 处理异常:本地代码可以处理一个未决的异常,本地方法实现能选择立即返回,引起异常在调用者中处理它;本地代码通过调用"ExceptionClear"能清理异常,然后执行它自己的异常处理代码。
- 在工具函数中的异常:
6.调用接口:
一个 Java 虚拟器实现是典型 作为一个本地库的运用。本地应用程序能针对这个库链接和使用载入 Java 虚拟机的调用接口。
这代码条件编译一个初始化结构"JDK1_1InitArgs",这结构明确虚拟器在"JDK release 1.1"上实现:
#include <jni.h>
#define PATH_SEPERATOR ';‘
#define PATH_CLASSPATH '.'
main(){
JNIEnv *env ;
JavaVM *jvm ;
jint res ;
jclass cls ;
jmethodID mid ;
jstring jstr ;
jclass stringClass ;
jobjectArray args ;
#ifdef JNI_VERSIO_1_2
JavaVMInitArgs vm_args ;
JavaVMOption options[1] ;
options[0].optionString = "-Djava.class.path="USERCLASSPATH ;
vm_args.version = 0x00010002 ;
vm_args.options = options ;
vm_args.ignoreUnrecognized= JNI_TRUE ;
res = JNI_CreateJavaVM(&jvm, (Void **)&env, &vm_args) ;
#else
JDK1_1InitArgs vm_args ;
char classpath[1024] ;
vm_args.version = 0x00010001 ;
JNI_GetDefaultJavaVMInitArgs(&vm_args) ;
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPERATOR, USER_CLASSPATH) ;
vm_args.classpath = classpath ;
res = JNI_CreateJavaVM(&jvm, &env, &vm_args) ;
#endif
if ( res < 0 ){
fprintf(stderr, "Can't create Java VM\n") ;
exit(1) ;
}
cls = (*env)->FindClass(env, "Prog") ;
if ( cls == NULL ){
goto destroy ;
}
mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V") ;
if ( mid == NULL ){
goto destory ;
}
jstr = (*env)->NewStringUTF(env, " From C!") ;
if( jstr == NULL ){
goto destory ;
}
stringClass = (*env)->FindClass(env,"java/lang/String") ;
args = (*env)->NewObjectArray(env, 1, stringClass, jstr) ;
if( args == NULL ){
goto destory ;
}
(*env)->CallStaticVoidMethod(env, cls, mid, args) ;
destroy:
if( (*env)->ExceptionOccurred(env) ){
(*env)->ExceptionDescribe(env) ;
}
(*jvm)->DestroyJavaVM(jvm) ;
}