您的位置:首页 > 编程语言 > Java开发

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

2016-02-02 10:05 633 查看

【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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java jni