本地方法(JNI)——访问数组元素+错误处理

【0】README

1) 本文文字描述 均转自 core java volume 2 , 旨在理解 本地方法(JNI)——访问数组元素+错误处理 的基础知识 ;
2)for source code, please visit : https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter12/chapter12_8


【1】本地方法(JNI)——访问数组元素

1)元素类型:

  • 1.1)Object: Get/SetObjectArrayElement
  • 1.2)基本类型: Get/SetXxxArrayElement + ReleaseXxxArrayElements

2) java 编程语言的所有数组类型都有相应的 C 语言类型, 如表12-2所示:

  • 2.1)GetArrayLength 函数: 返回数组的长度;

    jarray array = ......;
    jsize length = (*env)->GetArrayLength(env, array);
  • 2.2)怎样访问数组元素: 这取决于数组中存储的是对象还是基本类型的数据

  • 2.3)可以通过 GetObjectArrayElement 和 SetObjectArrayElement 方法: 访问对象数组的元素;

    jobjectArray array = ....;
    jobject x = (*env)->GetObjectArrayElement(env, array, i);
    (*env)->SetObjectArrayElement(env, array, j, x);
    以上方法效率非常低下;

2.4)GetXxxArrayElement函数: 返回一个指向数组起始元素的C 指针;
2.5)ReleaseXxxArrayElements 函数: 当你不再需要改指针时, 必须记得要调用 ReleaseXxxArrayElements 函数 通知虚拟机;

Attention)

  • A1)这里的 Xxx 必须是基本类型,不能是Object;
  • A2) 由于指针可能会指向一个 副本, 只有调用相应的 ReleaseXxxArrayElements 函数时, 你所做的改变才能保证在源数组里得到反映;

3)看个荔枝: 下面是对double 类型数组中的所有元素乘以一个常量的示例代码。 我们获取一个 java 数组的C 指针a, 并用 a[i] 访问各个元素;

jdoubleArray array = ...;
double scale = ...;
double *a = (*env)->GetDoubleArrayElements(env, array_a, NULL);
for(i=0; i<(*env)->GetArrayLength(env, array_a); i++)
        a[i] = a[i] * scale;
(*env)->ReleaseDoubleArrayElements(env, array_a, a, 0);

4)虚拟机是否确实需要对数组进行拷贝: 这取决于他是如何分配数组和如何进行垃圾回收的。 有些拷贝型的垃圾回收器例行进行移动对象,并更新对象引用;
5)该策略与 将数组锁定在 特定位置是不兼容的, 因为回收器不能更新本地代码中的指针值;
6) GetXxxArrayRegion和 SetXxxArrayRegion 函数: 能把一定范围内的元素从 java 数组复制到 C 数组中或从 C 数组复制到 java 数组中;
7)NewXxxArray 函数: 该函数在本地方法中创建新的 java 数组;


【2】错误处理

1)problem+solution:

  • 1.1)problem: C的运行期系统对数组越界错误, 不良指针造成的间接错误等不提供任何防护;而C语言没有异常;
    1.2)solution: 必须调用 Throw 或 ThrowNew 函数来创建一个新的异常对象。 当本地方法退出时, java 虚拟机就会抛出该异常;

2)NewObject 方法: 要使用 Throw函数,就需要使用 NewObject 来创建一个 Throwable 子类的对象。
3)看个荔枝:我们分配了一个EOFException 对象,然后将其抛出:

jclass class_EOFException = (*env)->FindClass(env, "java/io/EOFException"); //获取类;
jmethodID id_EOFException = (*env)->GetMethodID(env, class_EOFException , "<init>", "()V"); //获取方法标识符;
jthrowable obj_exc = (*env)->NewObject(env, class_EOFException ,id_EOFException); // 创建一个 Throwable 子类对象;
(*env)->Throw(env, obj_exc);  //抛出异常;

4)通常调用ThrowNew 会更加方便: 因为只需要提供一个类和一个 “改良UTF-8”字节序列, 该函数就会构建一个异常对象;

> (*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/EOFException"), "Unexpected end of file");

5) Throw 和 ThrowNew 都仅仅只是发布异常, 他们不会中断本地方法的控制流。只有当该方法返回时, java 虚拟机才会抛出异常。所以,每一个对 Throw 和 ThrowNew 的调用语句之后总是紧跟着 return 语句; (干货——Throw 和 ThrowNew 都仅仅只是发布异常, 他们不会中断本地方法的控制流。)
6)problem+solution:

  • 6.1)problem:通常, 本地代码不需要考虑捕获java 异常。 但是,当本地方法调用java 方法时, 该方法可能会抛出异常;
  • 6.2)solution:在这类情况下, 本地方法应该调用 ExceptionOccured 方法来确认是否有异常抛出。 如果没有任何异常被挂起, 则下面的调用返回 NULL, 否则返回一个当前异常对象 的引用;
jthrowable obj_exc = *(env)->ExceptionOccurred(env);
  • 6.3)如果只要检查是否有异常抛出: 调用, jboolean occured = (*env)->ExceptionCheck(env);

7)本地方法处理异常

  • 7.1)确定异常是否能够处理,如果能够处理, 必须调用下面的函数来关闭该异常:

    (*env)->ExceptionClear(env);

  • 7.2) 在我们的荔枝中, 我们实现了 fprint 本地方法, 这是基于该方法适合编写为本地方法的假设而实现的。下面是我们抛出的异常(exceptions):

    • e1) 如果格式字符串是NULL, 则抛出 空指针异常;
    • e2) 如果格式字符串不含适合打印 double 所需的 % 说明符, 则抛出 IllegalArgumentException异常;
    • e3)如果调用malloc 失败, 则抛出 OutOfMemoryException;
      这里写图片描述

    • 这里写图片描述

    • Attention) 本文给出的荔枝只po 了最后C语言抛出异常的结果, 没有将java 调用本地方法的steps 全部po出来。 因为博主我已经po这个steps , 都po厌烦了,我的本地(JNI)博文中有相应的 steps;for detailed steps , please visit http://blog.csdn.net/pacosonswjtu/article/details/50618022

8) 本地方法的C语言实现(source code at a glance , maybe you should attend for annotations below)

#include "Printf4.h"
#include <string.h>
#include <stdlib.h>
#include <float.h>

/**
   @param format a string containing a printf format specifier
   (such as "%8.2f"). Substrings "%%" are skipped.
   @return a pointer to the format specifier (skipping the '%')
   or NULL if there wasn't a unique format specifier
 */
char* find_format(const char format[])
{  
   char* p;
   char* q;

   p = strchr(format, '%');
   while (p != NULL && *(p + 1) == '%') /* skip %% */
      p = strchr(p + 2, '%');
   if (p == NULL) return NULL;
   /* now check that % is unique */
   p++;
   q = strchr(p, '%');
   while (q != NULL && *(q + 1) == '%') /* skip %% */
      q = strchr(q + 2, '%');
   if (q != NULL) return NULL; /* % not unique */
   q = p + strspn(p, " -0+#"); /* skip past flags */
   q += strspn(q, "0123456789"); /* skip past field width */
   if (*q == '.') { q++; q += strspn(q, "0123456789"); }
      /* skip past precision */
   if (strchr("eEfFgG", *q) == NULL) return NULL;
      /* not a floating-point format */
   return p;
}

JNIEXPORT void JNICALL Java_Printf4_fprint(JNIEnv* env, jclass cl, 
   jobject out, jstring format, jdouble x)
{  
   const char* cformat;
   char* fmt;
   jclass class_PrintWriter;
   jmethodID id_print;
   char* cstr;
   int width;
   int i;

   if (format == NULL) 
   {  
      (*env)->ThrowNew(env, /* ThrowNew 仅仅是发布异常,而不是抛出异常,当函数返回时才会抛出异常 */
         (*env)->FindClass(env,
         "java/lang/NullPointerException"),
         "Printf4.fprint: format is null");
      return;
   }

    /* 创建给定格式的字符串 */
   cformat = (*env)->GetStringUTFChars(env, format, NULL);
   fmt = find_format(cformat);

   if (fmt == NULL)
   {  
      (*env)->ThrowNew(env, /* ThrowNew 的作用同上  */
         (*env)->FindClass(env,
         "java/lang/IllegalArgumentException"),
         "Printf4.fprint: format is invalid");
      return;
   }

   width = atoi(fmt);
   if (width == 0) width = DBL_DIG + 10;
   cstr = (char*)malloc(strlen(cformat) + width);

   if (cstr == NULL)
   {  
      (*env)->ThrowNew(env,
         (*env)->FindClass(env, "java/lang/OutOfMemoryError"),
         "Printf4.fprint: malloc failed");
      return;
   }

   sprintf(cstr, cformat, x);

    /* 当你不再需要改指针时, 必须记得要调用 ReleaseXxxArrayElements 函数 通知虚拟机; */
   (*env)->ReleaseStringUTFChars(env, format, cformat);

   /* now call ps.print(str) */

   /* get the class ==获取类 */
   class_PrintWriter = (*env)->GetObjectClass(env, out);

   /* get the method ID == 获取方法标识 */
   id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(C)V");

   /* call the method == 通过方法标识调用方法  */
   for (i = 0; cstr[i] != 0 && !(*env)->ExceptionOccurred(env); i++)
      (*env)->CallVoidMethod(env, out, id_print, cstr[i]);

   free(cstr);
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值