使用JNI与原生代码的通信

1 方法的申明
有实例方法与静态方法。实例方法与类实例有关,它们只能在类实例中调用。静态方法不予类实例相关,它们可以在静态上下文直接调用。静态方法和实例均可以声明为原生的,可以通过JNI技术以原生代码的形式提供它们的实现。原生实例方法通过第二个参数获取实例引用,该参数是Jobject类型的。
 
JNIEXPORT jstring JNICALL Java_com_example_jni_JniTest_strFromJni(JNIEnv *env ,jobject thiz);
因为静态方法没有与实例绑定,因此通过第二个参数获取类引用而不是实例引用,第二个参数是jclass值类型的。
 
JNIEXPORT jstring JNICALL Java_com_example_jni_JniTest_strFromJni(JNIEnv *env ,jclass thiz);
正如方法定义中看到的,JNI提供了自己的数据类型从而让原生代码了解Java数据类型。
 
2 对引用数据类型的操作
 
2.1 字符串的操作
 JNI 支持 Unicode编码格式和UTF-8编码格式的字符串,还提供了两组函数通过JNIEnv接口指针处理这些字符串编码。
可以通过原生代码中用NewString 函数构建Unicode编码格式的字符串实例,用NewStringUTF函数构建UTF-8 编码格式的字符串实例。
 
jstring    (*NewString)(JNIEnv*, const jchar*, jsize);
jstring    (*NewStringUTF)(JNIEnv*, const char*);
 
 
为了在原生代码使用java字符串,需要先将Java字符串转换为C字符串,用GetStringChars函数可以将Unicode格式的java字符串转换成C字符串,用GetStringUTFChars函数可以将UTF-8格式的Java字符串转换成C字符串。这些函数的第三个参数均为可选参数,该参数是 isCopy, 它让调用者确定返回的c字符串地址指向副本还是堆中的固定对象。
 
const char * str;
jboolean  isCopy;
 
str = (*env)->GetStringUTFChars(env,javaString,&isCopy);
(*env)->ReleaseStringUTFChars(env,javaString,str);
 
释放字符串的操作 ReleaseStringUTFChars 释放UTF字符, ReleaseStringChars 释放Unicode编码
2.2 数组
 用New<Type>Array函数在原生代码中创建数组实例,其中<Type>可以是Int,Char 和Boolean 等。JNI提供两种访问Java数组元素的方法,可以将数组的代码复制成C数组或者让JNI提供直接指向数组元素的指针。
Get<Type>ArrayRegion 函数将给定的基本Java数组复制到给定的C数组中,
Set<Type> ArrayRegion 将C数组中的数据复制到Java数组中。这样的操作,如果数组过大的话,会引起性能问题,在这种情况下,如果可能的话,原生代码应该只获取或者设置数组元素区域而不是整个数组。
 
 
jint nativeInt[10];
jint result = 0;
(*env)->GetIntArrayRegion(env,intArray,0,10,nativeInt);
for(int i = 0 ; i < 10 ; i++){
    result = result + nativeInt[i];
    nativeInt[i] = nativeInt[i] + 5;
}
 
(*env)->SetIntArrayRegion(env,intArray,0,10,nativeInt);
 
直接获取数组的指针,这样就可以像普通C数组一样访问和处理数组元素,因此JNI没提供共访问和处理数组元素的方法,JNI要求原生代码用完这些指针立即释放,否则会出现内存溢出。原生代码可以使用JNI提供的Release<Type>ArrayElements 函数释放 Get<Type>ArrayElements 函数返回的C函数。
 
JNIEXPORT jboolean JNICALL Java_com_example_elvin_unit3_jni_JniTest_controlPointOfArray
        (JNIEnv *env, jobject thiz, jintArray intArray,jint size){
    jint * nativeDirectArray;
    jboolean isCopy;
    nativeDirectArray = (*env)->GetIntArrayElements(env,intArray,&isCopy);
    //指针是无法获取指向的内存空间内容的大小的
    for(int i = 0 ; i < size; i++){
        nativeDirectArray[i] =nativeDirectArray[i] + 5;
    }
    (*env)->ReleaseIntArrayElements(env,intArray,nativeDirectArray,0);
    return isCopy;
}
ReleaseIntArrayElements 最后一个参数是释放模式
0                           表示将内容复制回来,并释放原生数组
JNI_COMMIT        表示将内容复制回来,但是不释放原生数组,一般用于周期性的更新一个Java数组。
JNI_ABORT           释放原生数组但不用将内容复制回来
 
 
2.3 字段
Java有两类域:实例域和静态域。类的每个实例都有自己的实例域副本,而一个类的所有实例共享同一个静态域。
JNI提供了访问两类域的函数。
 
JNIEXPORT jstring JNICALL Java_com_example_elvin_unit3_jni_JniTest_getFiledFromJava
        (JNIEnv *env, jobject thiz){
    jclass clazz;
    clazz = (*env)->GetObjectClass(env,thiz);
    //获取id
    jfieldID instanceFieldId;
    instanceFieldId = (*env)->GetFieldID(env,clazz,"instanceFiled","Ljava/lang/String;");
    jfieldID staticFieldId;
    staticFieldId = (*env)->GetStaticFieldID(env,clazz,"staticFiled","Ljava/lang/String;");
    //获取实例域
    jstring instanceField;
    instanceField = (*env)->GetObjectField(env,thiz,instanceFieldId);
    jstring staticField;
    staticField = (*env)->GetStaticObjectField(env,clazz,staticFieldId);
 
    return instanceField;
}
 
 
2.4 方法
跟域一样,java中有两种方法,实例方法和静态方法。
 
JNIEXPORT jstring JNICALL Java_com_example_elvin_unit3_jni_JniTest_strFromJni
          (JNIEnv *env ,jobject thiz, jstring javastring){
//            return (*env)->NewString(env,"asdf",4);  //这是unicode
    jclass  clazz = (*env)->GetObjectClass(env,thiz);
    jmethodID instanceMethodId;
    instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");
    jstring instanceResult;
    instanceResult = (*env)->CallObjectMethod(env,thiz,instanceMethodId);
 
    jmethodID staticMethodId;
    staticMethodId = (*env)->GetStaticMethodID(env,clazz,"staticMethod","()Ljava/lang/String;");
    jstring staticResult;
    staticResult = (*env)->CallStaticObjectMethod(env,clazz,staticMethodId);  //这里第二个参数调用的是静态域
 
//        return (*env)->NewStringUTF(env,"asdf");  //这是utf
    return staticResult;
  }
 
原生代码之间转换的代价是很大的,强烈建议规划java代码和原生代码的任务是考虑这种代价,最小化这种转换可以大大提高应用程序的性能
 
2.5 异常处理
 异常处理是java程序设计的重要功能,JNI中的异常行为与JAVA中的中所有不同,在Java中,当抛出一个异常时,虚拟机停止执行代码块并进入调用栈反向检查能处理特定类型异常的处理程序代码块,这也叫作捕获异常。虚拟机清除异常并将控制权交给异常处理程序。相比之下,JNI要求开发人员在异常发生后显式的实现异常处理流。
 
2.6线程
2.6.1同步
 
void MultiThread(JNIEnv *env, jobject thiz){
    if(JNI_OK == (*env)->MonitorEnter(env,thiz)){
        //错误处理
    }
 
    //同步代码块
    if(JNI_OK == (*env)->MonitorExit(env,thiz)){
        // 错误处理
    }
}
 
 
2.6.2 原生线程
    为了执行特定任务,这些原声构件可以并行使用原生线程。因为虚拟机不知道原生线程,因此它们不能与Java构件直接通信。为了与应用的依然活跃部分交互,原生线程应该先附着在虚拟机上面。
     JVM 通过 java JVM 接口指针提供了AttachCurrentThread 函数以便于让原生代码将原生线程依附到虚拟机上, javaJVM接口应该今早被缓存,否则的话他不能被获取。
 
void jniThread(JNIEnv *env, jobject thiz){
  JavaVM * cacheJVM;
 
  //将线程附着到虚拟机
  (*cacheJVM)->AttachCurrentThread(cacheJVM,&env,NULL);
  // 可以用JNIENV 接口实现线程与java应用程序之间的通信
  //将当前线程与虚拟机分离
  (*cacheJVM)->DetachCurrentThread(cacheJVM);
}
 
 
posted @ 2017-08-29 15:31  抹茶蛋挞  阅读(352)  评论(0编辑  收藏  举报