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

Java本地接口(JNI)编程指南和规范(第三章)

2011-12-17 17:36 330 查看
原文链接:http://blog.sina.com.cn/s/blog_53988c0c0100osmx.html

第二部分: 编程者的指南(Part Two: Programmer's Guide)

 

第三章 基本类型,字符串和数组(Basic Types, Strings, and Arrays)

当面对带有本地代码的Java的应用程序时,程序员问的最通常的问之一,是在Java编程语言中的数据类型怎样对映到本地编程语言例如"C"和"C++"中的数据类型。在上一章节中出现的"Hello World!"例子中,我们没有传递任何参数到本地方法中,本地方法没有放回任何结果。本地方法简单地答应了一个消息和放回。

 

实际上,大多数程序将需要传递参数给本地方法,和也从本地方法接受结果。在这章节中,我们将描述怎样转换数据类型在用Java编程语言写的代码和实现本地方法的本地代码中。我们将从基本的类型开始,如整型(intergers)和普通的对象类型,如字符串(stirngs)和数组(arrays).我们推迟任意对象的彻底解决到下一章,在下一章我们将解释本地代码能怎样访问域和调用方法。

 

3.1 一个简单本地方法(A Simple Native Method)
让我们从一个简单的例子开始,这个例子不同于在上一章的"HelloWorld"程序。这个例子程序,"Prompt.java",是一个打印字符串,等待用户输入,然后返回一行用户的输入的本地方法。这个程序的源代码如下(as follows):

class Prompt{

 // native method that prints a prompt and read a line

 private native String getLine(String prompt) ;

 public static void main(String args[]){

  Prompt p = new Prompt() ;

  String input = p.getLine("Type a line: ") ;

  System.out.println("User typed:"+input) ;

 }

 static {

  System.loadLibrary("Prompt") ;

 }

}

 

"Prompt.main"调用本地方法"Prompt.getLine"来得到用户的输入。静态初始化调用了"System.loadLibrary"方法来在载入一个本地库"Prompt"。

 

3.1.1 为实现本地方法的C原型(C Prototype for Implementing the Native Method)

"Prompt.getLine"方法能用下面的"C"函数来实现:

JNIEXPORT jstring JNICALL

Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);

 

你能用"javah"工具来产生一个包含上面函数原型的同文件。"JNIEXPORT"和"JNICALL"宏(被定义在"jni.h"头文件里)确保这个函数从本地库中导出和"C"编译器产生带有为这个函数正确调用约定的代码。C函数的名字被格式为连接"Java_"前缀,类名和方法名。11.3部分包含了一个更精确的怎样格式化C函数名字的描述。

 

3.1.2 本地方法参数(Native Method Arguments)

在2.4部分中被简单的讨论,本地方法实现如"Java_Prompt_getLine"接受两个标准参数,除了参数在本地方法中被声明外。第一参数,"JNIEnv"接口指针,指向一个包含指向函数表的地方。在函数表中每个条目都指向一个"JNI"函数.本地方法总是通过这些"JNI"函数的一个来访问在Java虚拟器中的数据结构。"Figure 3.1"说明"JNIEnv"接口指针。

JNIEnv *

   |

   |--->  pointer               ---->  Pointer ---> an interface function

          (Internal virtual            Pointer ---> an interface function

          machine data                 Pointer ---> an interface function

          structures)                  ...

Figure 3.1 the JNIEnv Interface Pointer

 

对于本地方法是一个静态或是一个实例方法,第二个参数不同。对一个实例化的本地方法的第二个参数,是一个关于被调用方法的对象的参考,类似于在"C++"中的"this"指针。对于一个静态的本地方法的第二个参数,是一个定义这个方法的类的参考。我们的例子,"Java_Prompt_getLine",实现了一个实例化的本地方法。因此这个"jobject"参数是对象(object)自己的参考。

 

3.1.3 类型的映射(Mapping of Types)

在本地方法声明中参数类型有对应的在本地编程语言中的类型。"JNI"定义了一套"C"和"C++"类型来对应在"Java"编程语言中的类型。

 

在"Java"编程语言中的两种类型:基本来型如"int,float",和"char";和参考类型如"classes","instances"和"arrays".在"Java"编程语言中,strings是"java.lang.String"类的一个实例。

 

"JNI"不同地对待基本类型和参考类型。基本类型的映射是简单易懂的。例如,在"Java"编程语言中的"int"类型映射到"C/C++"的"jint"类型(定义在"jni.h"作为一个有符号32bit整型),同时在"Java"编程语言中的"float"类映射到"C++"的"jfloat"类型(定义在"jni.h"作为一个有符号32bit浮点数),12.1.1部分包含了在"JNI"中定义的所有基本类型的定义。

 

"JNI"传递"objects"到本地方法作为不透明的引用(opaque references)。不透明的引用是一个C指针类型,引用了在Java虚拟机中的内部数据结构。然而,内部数据结构的精确安排,对编程者是隐藏的。本地代码必须通过恰当的"JNI"函数处理下面的对象(objects),"JNI"函数通过"JNIEnv"接口指针是可用的。例如,为"java.lang.String"对应的"JNI"类型是"jstring"。一个"jstring"引用(reference)的确切值是和本地代码无关的。本地代码调用"JNI"函数例如"GetStringUTFChars"来访问一个string的内容。

 

所有的"JNI"引用都是类型jobject。为了方便和增强类型的安全,"JNI"定义了一组引用类,它们概念上为"jobject"的子类型("subtypes").(A是B的子类,A的实例也是B的实例。)这些子类对应在"Java"编程语言中常用地引用类型。例如,"jstring"指示"strings";"jobjextArray"指示一组"objects"。12.1.2部分包含"JNI"引用类型和其他关系的资料性的完整列表。

 

3.2 访问Strings(Accessing Stirngs)

"Java_Prompt_getLine"函数接受"prompt"的一个"jstring"类型作为参数."jstring"类型表示在Java虚拟机中的"strings",同时和规定的“C string"类型不同(指向字符的指针,char *).你不能用一个一般"C string"来作为一个"jstring"来使用。下面的代码,如果运行,它们不能产生想要的结果。事实上,它将很可能使Java虚拟机奔溃。

JNIEXPORT jstring JNICALL

Java_Prompt_getLine(JNIEnv *env, jobjext obj, jstring prompt)

{

 

 printf("%s", prompt) ;

 ......

}

 

3.2.1 转换到本地字串(Converting to Native Strings)

你的本地方法代码必须使用恰当的"JNI"函数来转化"jstirng objects"为"C/C++ strings"。"JNI"支持转换到或从"Unicode"和"UTF-8"的"strings"。"Unicode strings"表示字符是16bit的值,而"UTF-8 strings"使用了一编码规则,它享受兼容了"7-bit ASCII strings"。"UTF-8 strings"有想"C strings"空符号结尾,即使他们包含非"ASCII"(non-ASCII)字符.所有7-bit ASCII字符的值在1到127之间,在UTF-8编码中任然保留了。一个"byte"的最高"bit"被设置为1,标记一个多"byte"编码"16-bit
Unicode"的值的开始。

 

"Java_Prompt_getLine"函数调用"JNI"函数"GetStringUTFChars"来阅读"string"的内容。通过"JNIEnv"的接口指针m"GetStringUTFChars"函数是能被调用的。它转换了作为一个"Unicode"序列通过Java虚拟机的实现来表示"jstring"的引用到用"UTF8" 格式表示的一个”C string"。如果你确定一个原始的字符只包含"7-bit ASCII"字符, 你可以传递转换字符串到到规定"C"库函数例如"printf"。(我们讨论怎样处理"non-ASII
string" 在8.2部分。)

JNIEXPORT jstring JNICALL

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) ;

}

 

不要忘记检查"GetStringUTFChars"的返回值。因为Java虚拟机实现需要分配空间来存储"UTF-8 string",这有有机会内配失败。当这样的事发生时,"GetStringUTFChars"返回"NULL",同时通过"JNI"抛出一个异常,它不同于在Java编程语言中抛出的一个异常。通过"JNI"抛出的一个未决的异常不自动地改变在本地"C"代码中的控制流程。替代是,我们需要发布一个清楚的返回声明来在"C"函数中跳过剩余的语句。在"Java_Prompt_getLine"返回后,在"Prompt.main"中抛出这个异常,"Prompt.getLine"的本地方法的调用者。

 

3.2.2 释放本地字符资源(Freeing Native String Resources)

当你本地代码结束使用通过"GetStringUTFChars"得到的"UTF-8 string"时,它调用"ReleaseStringUTFChars"。调用"ReleaseStringUTFChars"指明本地方法不再需要这"GetStringUTFChars"返回的"UTF-8 string"了;因此"UTF-8 string"占有的内存将被释放。没有调用"ReleaseStringUTFChars"将导致内存泄漏,这将可能最终导致内存的耗尽。

 

3.2.3 构建新的字符串(Constructing New Strings)

在本地方法中通过调用"JNI"函数"NewStringUTF",你能构建一个新的"java.lang.String"实例。"NewStringUTF"函数使用一个带有"UTF-8"格式的"C string",同时构建一个"java.lang.String"实例。最新被构建的"java.lang.String"实例表现为和被给的"UTF-8 C string"一样的"Unicode"字符序列。

 

如果虚拟机没有内存分配来构建"java.lang.String"实例,"NewStringUTF"抛出一个"OutofMemoryError"异常,同时返回"NULL"。在这个例子中,我们不需要检查这个来放回值,因为本地方法过后立即返回了。如果"NewStringUTF"失败,"OutOfMemoryError"异常将在"Prompt.main"方法中被抛出,来说明本地方法的调用。。如果"NewStringUTF"成功,它返回一个"JNI"的最新构建的"java.lang.String"实例的引用。这最新实例被"Prompt.getLine"放回,然后赋给在"Prompt.main"中的"input"局部变量。

 

3.2.4 其他的JNI字符串函数(Other JNI String Functions)
"JNI"支持大量的其他字符串相关的函数(string-related functions),除了前面介绍的"GetStringUTFChars","ReleaseStringUTFChars"和"NewStringUTF"函数。

 

"GetStringChars"和"ReleaseStringChars"获得字符串使用"Unicode"格式。例如,在操作系统支持Unicode为本地字符串格式的时候,这些函数有用。

 

"UTF-8 string"总是以"\0"字符结尾,Unicode字符不是。为在一个"jstring"引用中得到Unicode字符的个数,JNI程序员能调用"GetStringLength"。为得到用"UTF-8"格式表示的一个"jstring"需要的"bytes"数,"JNI"程序员可以调用ASCII C函数strlen在"GetStringUTFChars"的结果上,或直接地调用"JNI"函数"GetStringUTFLength"在"jstring"引用上。

 

"GetStringChars"和"GetStringUTFChar"的第三个参数的需要额外的解释:

const jchar *

GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy) ;

 

当来自"GetStringChars"返回时,通过"isCopy"被设置为"JNI_TRUE",返回的字符串是个在原始"java.lang.String"实例的字符串的一个复制,内存定位指向它。通过"isCopy"被设置为"JNI_FALSE",返回字符串时一个直接指向在原始的"java.lang.String"实例中的字符串,内存定位指向它。当通过"isCopy"被设置为"JNI_FALSE",内存定位指向的字符串时,本地代码必须不能修改返回的字符串的内容。违反了这个规则将引起原始"java.lang.String"实例也被修改。这破坏了"java.lang.String"永恒不可变性。

 

你最常传递"NULL"作为"isCopy"参数,因为你不关心Java虚拟机是否返回在"java.lang.String"实例中的一个复制字符串和直接指向原始字符串。

 

一般不可能预测虚拟机将是不是复制字串对一个给定的"java.lang.String"实例。因此程序员必须假设函数如"GetStringChars"可以使用对应的空间和时间到在"java.lang.String"实例中的大量字符上。在一个典型Java虚拟器实现中,垃圾收集器搬迁堆上的对象。一旦直接指向一个"java.lang.String"实例的指针被传递给本地代码,垃圾收集器再不能搬移这"java.lang.String"实例了。换另一种说法,虚拟器必须固定住"java.lang.String"实例。因为过多地固定导致内存碎片,虚拟器实现可以,为灭个单独地"GetStringChars"调用,酌情地决定是复制字串还是固定实例。

 

不要忘记调用"ReleaseStringChars",当你不再需要访问来自"GetStringChars"返回的"string"元素的时候。"ReleaseStringChars"调用是必须的,无论"GetStringChars"设置"* isCopy"为"JNI_TRUE"或"JNI_FALSE"。"ReleaseStringChars"释放副本,或解除实例的固定,这依赖"GetStringChars"是返回一个副本还是指向实例。

 

3.2.5 在"Java 2 SDK Release 1.2"中新建JNI字符串函数(New JNI String Functions in Java 2 SDK Release 1.2)

为了增加虚拟器能够返回直接指向在一个"java.lang.String"实例中的字符串的可能,"Java 2 SDK release 1.2"介绍了一对新函数,"Get/ReleaseStringCritical"。表面上,它们表现和"Get/ReleaseStringChars"函数相似。如果可能,在那两个函数中返回字符串的指针;否则产生一个副本。然而,这些函数怎样使用要有充分地限制。

 

你必须把这对函数中的代码放在一个排斥关键区域(critical region)中运行对待.在这个排斥关键域中,本地代码不应该调用任意"JNI"函数,或者任何可以引起当前线程阻塞和等待另一运行在Java虚拟器中的个线程的本地函数。例如,当前线程不应该等待在一个I/O流上的输入,这I/O是被另一个线程写入的。

 

这些限制使虚拟器能够暂停垃圾收集,当本地代码通过"GetStringCritical"来获得直接指向"string"的指针时。当垃圾收集暂停时,任何其他引发垃圾收集的线程也将本阻塞。在"Get/ReleaseStringCritical"对之间的本地代码,不应该出现阻塞调用或在Java虚拟器中分配新对象。否则,虚拟器可能锁死。思考下面的情况:

.另一线程出发垃圾收集不能进展下去,指导当前线程结束阻塞调用和使垃圾收集再能够。

.然而,当前线程不能进行下去,因为这阻塞调用需要获得一个已经被另一个等待执行垃圾收集的线程持有的锁。

 

交替的多对"GetStringCritical"和"ReleaseStringCritical"函数的使用时安全的。例如:

jchar *s1, *s2 ;

s1=(*env)->GetStringCritical(env, jstrl) ;

if ( s1 == NULL) {

 ....

}

.....

(*env)->ReleaseStringCritical(env, jstr1,s1) ;

(*env)->ReleaseStringCritical(env, jstr2,s2) ;

 

"Get/ReleaseStringCritical"对不需要严格的以一栈顺序来嵌套。我们不该忘记检查它们的返回值,阻止为内存状态可能是"NULL",因为如果VM内部用不同格式来表示数组,"GetStringCritical"可以分配一个"buffer"同时复制一个数组副本。例如,Java虚拟器(VM)可以不连续地保存数组。在这个例子中,"GetStringCritical"必须复制在"jstring"实例中的说有的字符为了返回本地代码一个连续的字符数组。

 

为避免锁死,你必须确信本,在调用"GetStringCritical"后和在调用"ReleaseStringCritical"前,地代码没有调用任何JNI函数。在排斥关键域中被允许调用的"JNI"函数是重载的"Get/ReleaseStringCritical"和"Get/ReleasePrimitiveArrayCritical"调用。

 

"JNI"不支持"GetStringUTFCritical"和"ReleaseStringUTFCritical"函数。这样函数可能需要虚拟器来复制一份"string",因为虚拟器实现大都在内部用"Unicode"编码表示"strings"。

 

对于"Java 2 SDK release 1.2"的其他附加是"GetStringRegion"和"GetStringUTFRegion"。这些函数复制"string"元素到预分配的"buffer"中。"Prompt.getLine"方法可以使用"GetStringUTFRegion"来实现的如下:

JNIEXPORT jstring JNICALL

Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)

{

 

 char outbuf[128], inbuf[128] ;

 int len = (*env)->GetStringLength(env, prompt) ;

 (*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf) ;

 printf("%s", outbuf) ;

 scanf("%s", inbuf) ;

 return (*env)->NewStringUTF(env, inbuf) ;

}

 

"GetStringUTFRegion"函数使用一个开始索引和长度,这两个备用来计算Unicode字符的个数。函数也执行了边际检查,和如果必要放出"StringIndexOutOfBoundsException"。在上面代码中,我们从自己的"string"引用中得到长度,因此确保不会索引溢出。(然而上面的代码缺少必要检查来确保"prompt string"的字符少于128。)

 

这个代码是比"GetStringUTFChars"稍微更简单的使用。因为"GetStringUTFRegion"不执行内存的分配,我们不需要检查可能没有内存的情况。(再说一次,上面的代码缺少必要检查来确保"prompt string"的字符少于128。)

 

3.2.6 "JNI String"函数的总结(Summary of JNI String Functions)

Table 3.1摘要所有字符串相关的"JNI"函数。"Java 2 SDK 1.2 release"增加一写新的函数,来增强对某些字符串操作的执行。这些增加的函数不支持新的操作,而是带来性能的改善。

Table 3.1 Summary of JNI String Functions

JNI Function                Description                             since

GetStringChars              Obtains or release a pointer to the     JDK1.1

RetleaseStringChars         contents of a string in Unicode format.

                            May return a copy of the string.

GetStringUTFChars           Obtains or release a pointer to the     JDK1.1

ReleaseStringUTFChars       contents of a string in UTF-8 format.

                            May return a copy of the string.

GetStringLength             Returns the number of Unicode char-     JDK1.1

                            acters in the string.

GetStringUTFLength          Returns the number of bytes needed      JDK1.1

                            (not including the trailing 0) to repre-

                            sent a string in the UTF-8 Format.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息