序这四种情况下你会用到本书:1、在Java程序中复用以前写过的C/C++代码。2、自己实现一个java虚拟机3、学习不同语言如何进行协作,尤其是如何实现垃圾回收和多线程。4、把一个虚拟机实现整合到用C/C++写的程序中。本书是写给开发者的。JNI在1997年第一次发布,本书总结了SUN工程师和大量开发者两年来积累的经验。本书介绍了JNI的设计思想,对这种思想的理解是使用JNI的各种特性的基础。本书有一部分是JAVA2平台上面的JNI特征的规范说明。JNI程序员可以把这部分用作一个手册。JVM开发者在实现虚拟机的时候必须遵守这些规范。JNI的部分设计思想来源于Netscape的JavaRuntimeInterface(JRI)。第一章简介JNI是JAVA平台的一个重要特征,使用它我们可以重用以前用C/C++写的大量代码。本书既是一个编程指南也是一个JNI手册。本书共包括三部分:1、第二章通过一个简单的例子介绍了JNI。它的对象是对JNI不熟悉的初学者。2、3~10章对JNI的特征进行了系统的介绍。我们会举大量的例子来说明JNI的各个特征,这些特征都是JNI中重要且常用的。3、11~13章是关于JNI的技术规范。可以把这两章当作一个手册。本书尽量去满足各类读者的需要。指南面向初学者,手册面向有经验的人和自己实现JNI规范的人。大部分读者可能是用JNI来写程序的开发者。本书会假设你有JAVA,C/C++基础。本章的剩余部分介绍了JNI的背景,扮演的角色和JNI的演化。1.1JAVA平台和系统环境(HostEnvironment)系统环境代指本地操作系统环境,它有自己的本地库和CPU指令集。本地程序(NativeApplications)使用C/C++这样的本地语言来编写,被编译成只能在本地系统环境下运行的二进制代码,并和本地库链接在一起。本地程序和本地库一般地会依赖于一个特定的本地系统环境。比如,一个系统下编译出来的C程序不能在另一个系统中运行。1.2JNI扮演的角色JNI的强大特性使我们在使用JAVA平台的同时,还可以重用原来的本地代码。作为虚拟机实现的一部分,JNI允许JAVA和本地代码间的双向交互。图1.1JNI的角色JNI可以这样与本地程序进行交互:1、你可以使用JNI来实现“本地方法”(nativemethods),并在JAVA程序中调用它们。2、JNI支持一个“调用接口”(invocationinterface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。例如,一个用C语言写的浏览器可以在一个嵌入式JVM上面执行从网上下载下来的applets1.3JNI的副作用请记住,一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:1、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。2、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了JAVA和C之间的耦合性。1.4什么场合下应该使用JNI当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。像上一节所提到的,应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:1、JAVA程序和本地程序使用TCP/IP或者IPC进行交互。2、当用JAVA程序连接本地数据库时,使用JDBC提供的API。3、JAVA程序可以使用分布式对象技术,如JAVAIDLAPI。这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。下面这些场合中,同一进程内JNI的使用无法避免:1、程序当中用到了JAVAAPI不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。3、JAVA程序当中的一部分代码对效率要求非常高,如算法计算,图形渲染等。总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。1.5JNI的演化JDK1.0包含了一个本地方法接口,它允许JAVA程序调用C/C++写的程序。许多第三方的程序和JAVA类库,如:java.lang,java.io,java.net等都依赖于本地方法来访问底层系统环境的特征。不幸的是,JDK1.0中的本地方法有两个主要问题:1、本地方法像访问C中的结构(structures)一样访问对象中的字段。尽管如此,JVM规范并没有定义对象怎么样在内存中实现。如果一个给定的JVM实现在布局对象时,和本地方法假设的不一样,那你就不得不重新编写本地方法库。2、因为本地方法可以保持对JVM中对象的直接指针,所以,JDK1.0中的本地方法采用了一种保守的GC策略。JNI的诞生就是为了解决这两个问题,它可以被所有平台下的JVM支持:1、每一个VM实现方案可以支持大量的本地代码。2、开发工具作者不必处理不同的本地方法接口。3、最重要的是,本地代码可以运行在不同的JVM上面。JDK1.1中第一次支持JNI,但是,JDK1.1仍在使用老风格的本地代码来实现JAVA的API。这种情况在JDK1.2下被彻底改变成符合标准的写法。1.6例子程序本书包含了大量的代码示例,还教我们如何使用javah来构建JNI程序。第二章开始。。。本章通过一个简单的例子来示例如何使用JNI。我们写一个JAVA程序,并用它调用一个C函数来打印“HelloWorld!”。2.1概述图2.1演示了如何使用JAVA程序调用C函数来打印“HelloWorld!”。这个过程包含下面几步:1、创建一个类(HelloWorld.java)声明本地方法。2、使用javac编译源文件HollowWorld.java,产生HelloWorld.class。使用javah–jni来生成C头文件(HelloWorld.h),这个头文件里面包含了本地方法的函数原型。3、用C代码写函数原型的实现。4、把C函数实现编译成一个本地库,创建Hello-World.dll或者libHello-World.so。5、使用java命令运行HelloWorld程序,类文件HelloWorld.class和本地库(HelloWorld.dll或者libHelloWorld.so)在运行时被加载。图2.1编写并运行“HelloWorld”程序本章剩余部分会详细解释这几步。第三章基本类型、字符串、数组开发者使用JNI时最常问到的是JAVA和C/C++之间如何传递数据,以及数据类型之间如何互相映射。本章我们从整数等基本类型和数组、字符串等普通的对象类型开始讲述。至于如何传递任意对象,我们将在下一章中进行讲述。3.1一个简单的本地方法JAVA端源代码如下:classPrompt{//nativemethodthatprintsapromptandreadsalineprivatenativeStringgetLine(Stringprompt);publicstaticvoidmain(Stringargs[]){Promptp=newPrompt();Stringinput=p.getLine(Typealine:);System.out.println(Usertyped:+input);}static{System.loadLibrary(Prompt);}}3.1.1本地方法的C函数原型Prompt.getLine方法可以用下面这个C函数来实现:JNIEXPORTjstringJNICALLJava_Prompt_getLine(JNIEnv*env,jobjectthis,jstringprompt);其中,JNIEXPORT和JNICALL这两个宏(被定义在jni.h)确保这个函数在本地库外可见,并且C编译器会进行正确的调用转换。C函数的名字构成有些讲究,在11.3中会有一个详细的解释。3.1.2本地方法参数第一个参数JNIEnv接口指针,指向一个个函数表,函数表中的每一个入口指向一个JNI函数。本地方法经常通过这些函数来访问JVM中的数据结构。图3.1演示了JNIEnv这个指针:图3.1JNIEnv接口指针第二个参数根据本地方法是一个静态方法还是实例方法而有所不同。本地方法是一个静态方法时,第二个参数代表本地方法所在的类;本地方法是一个实例方法时,第二个参数代表本地方法所在的对象。我们的例子当中,Java_Prompt_getLine是一个实例方法,因此jobject参数指向方法所在的对象。3.1.3类型映射本地方法声明中的参数类型在本地语言中都有对应的类型。JNI定义了一个C/C++类型的集合,集合中每一个类型对应于JAVA中的每一个类型。JAVA中有两种类型:基本数据类型(int,float,char等)和引用类型(类,对象,数组等)。JNI对基本类型和引用类型的处理是不同的。基本类型的映射是一对一的。例如JAVA中的int类型直接对应C/C++中的jint(定义在jni.h中的一个有符号32位整数)。12.1.1包含了JNI中所有基本类型的定义。JNI把JAVA中的对象当作一个C指针传递到本地方法中,这个指针指向JVM中的内部数据结构,而内部数据结构在内存中的存储方式是不可见的。本地代码必须通过在JNIEnv中选择适当的JNI函数来操作JVM中的对象。例如,对于java.lang.String对应的JNI类型是jstring,但本地代码只能通过GetStringUTFChars这样的JNI函数来访问字符串的内容。所有的JNI引用都是jobject类型,对了使用方便和类型安全,JNI定义了一个引用类型集合,集合当中的所有类型都是jobject的子类型。这些子类型和JAVA中常用的引用类型相对应。例如,jstring表示字符串,jobjectArray表示对象数组。3.2访问字符串Java_Prompt_getLine接收一个jstring类型的参数prompt,jstring类型指向JVM内部的一个字符串,和常规的C字符串类型char*不同。你不能把jstring当作一个普通的C字符串。3.2.1转换为本地字符串本地代码中,必须使用合适的JNI函数把jstring转化为C/C++字符串。JNI支持字符串在Unicode和UTF-8两种编码之间转换。Unicode字符串代表了16-bit的字符集合。UTF-8字符串使用一种向上兼容7-bitASCII字符串的编码协议。UTF-8字符串很像NULL结尾的C字符串,在包含非ASCII字符的时候依然如此。所有的7-bitASCII字符的值都在1~127之间,这些值在UTF-8编码中保持原样。一个字节如果最高位被设置了,意味着这是一个多字节字符(16-bitUnicode值)。函数Java_Prompt_getLine通过调用JNI函数GetStringUTFChars来读取字符串的内容。GetStringUTFChars可以把一个jstring指针(指向JVM内部的Unicode字符序列)转化成一个UTF-8格式的C字符串。如何你确信原始字符串数据只包含7-bitASCII字符,你可以把转化后的字符串传递给常规的C库函数使用,如printf。我们会在8.2中讨论如何处理非ASCII字符串。JNIEXPORTjstringJNICALLJava_Prompt_getLine(JNIEnv*env,jobjectobj,jstringprompt){charbuf[128];constjbyte*str;str=(*env)-GetStringUTFChars(env,prompt,NULL);if(str==NULL){returnNULL;/*OutOfMemoryErroralreadythrown*/}printf(%s,str);(*env)-ReleaseStringU