JNI用在哪里
在你用JNI写一个项目前,返回去研究一下修改的解决方案是否更适宜是很值得的。就像上一节提到的,与用java写的应用程序相比,用JNI写的应用程序也带有JIN(如:C、C++)的不安全的缺点(java没有指针,C、C++有指针,指针不安全)。例如,你失去了java编程语言类型安全的优点。
一些修改方法也允许java和其他一些编程语言进行互操作。例如:
a.一个java应用程序可以用TCP/IP连接或进程间通信来于本地程序通信。
b.一个java应用程序可以通过JDBCAPI与一个传统的数据库相连。
c.Java应用程序可以利用对象等分布技术的优势,例如:JavaIDL API.
修改方案的一个共同特征是javaapp和native code可以驻留在不同的进程(有时在不同的机器)。进程分开提供了一个很大的好处。。。。。。。
有时候,你可能发现javaapp 与驻留在同一进程中nativecode 间通行是必要的。这时JNI就要其作用了。考虑一下下面的情况:
a.。。。。。。
b.。。。。。。
c.。。。。。。
d.。。。。。。
总结一下,如果你的javaapp必须和驻留在同一进程中的nativecode互通信,你必须使用JNI来实现这一互通信功能。
JNI的演变历程
Javaapp 和native code的交互操作在java平台的早期版本中已有应用。java平台的第一个发行版本,即JDK1.0,也包含了一个本地调用接口允许java平台调用像C++、C这样的语言写的nativecode。许多第三方应用既可以实现java类库(如,java.lang和java.io、java.net),也要依靠第三方应用去实现本地调用接口函数的底层主机环境。
不幸的是,JDK1.0中的本地调用函数有两个问题:
第一,
第二,
提出JNI是用来克服这些问题的。JNI是一个可以被所有JVM支持的,可以广泛实现主机环境的接口调用。JNI特征:
1.每个虚拟机的实现者可以支持大量的nativecode。
2.开发工具供应商不必处理不同种类的本地方法接口。
3.最重要的是,appprogrammers 只需写一个版本的nativecode ,而可以运行在不同平台上的JVM中。
JNI在JDK1.1发行版中首次被支持。然而,JDK1.1任久用旧版本的本地调用方法(就像JDK1.0中的一样)去实现javaAPI,这些在JDK2.0中已经不是问题了。本地方法被重写以适应JNI标准。
JNI是一个被所有JVM实现了的本地调用接口函数。从JDK1.1开始,你就可以编写JNI程序。旧风格的本调用接口函数任就被JDK2支持,在未来更先进JVM中不再支持。
JDK2包含了许多JNI的增强,这些增强将向后兼容,JNI未来将主要完成对二进制的兼容。
程序示例
这本书包含了大量的程序示例以演示JNI的特性。
示例程序由许多用java语言和用C++、C native code实现的代码段组成。有时候 nativecode 在Solaris 和Win32中进行了特画。我们在JDK2环境下展示了如何用命令行(如,javah)来编译JNI程序。
提醒一下,JNI的使用不局限于特有环境和特有应用程序开发工具。这本书注重代码实现,不是用工具编译或运行代码。捆绑JDK2的命令行工具是相当原始的做法。第三方工具提供了一个改进的用来编译JNIapp的方式。我们建议你查询与你选择的开发工具绑定的与JNI有关的帮助文档。
你可以从下面网站下载这本书上的示例源码,和这本书的最新更新,地址:
http://java.sun.com/docs/books/jni/
第二章 让我们开始吧!
这一章带你纵览用JNI写的一个简单例子。我们将写一个Javaapp 来调用C程序,实现打印“HelloWorld!”。
概述
图2.1演示了用JDK2写一个简单的Javaapp 来调用 C程序打印“HelloWorld!”的过程。这个过程由下面这几个步骤组成:
1.创建一个类(HelloWorld.java)声明nativemethod。
2.用javac编译HelloWorld源文件,生成class文件HelloWorld.class。Javac编译器支持JDK2.0发行版。
3.用javah-jni 生成 一个包含nativecode中实现的方法的原型的Cheader file(HelloWorld.h)。javah工具也由JDK2提供。
4.写一个由C实现的native程序(HelloWorld.c)。
5.把c实现文件编译成一个nativelib,创建 HelloWorld.dll或 libHelloWorld.so。使用主机环境上的可用的C编译器和链接器。
6.使用javaruntime 解释运行HelloWorld程序。class file(HelloWorld.class)和natvielibrary(libHelloWorld.so)都在runtime上运行。
Figure2.1
这章剩下的部分解释这些步骤的细节。
声明本地方法
用java语言写下面的程序。
这个程序定义了一个包含一个名叫print的native方法的名叫HelloWorld的类。
ClassHelloWorld{
privatenative void print();
publicstatic void main(String[] args){
newHelloWorld().print();
}
static{
System.loadLibrary(“HelloWorld”);
}
}
HelloWorld类开始声明一个native方法。紧接着定义了一个main方法,其中实例化了一个HelloWorld对象用来调用print native方法。在类的最后一部分是一个staticinitializer,其用来加载一个实现了printnative方法的native库。
在native方法的声明和一般java程序语言的声明中存在两点不同之处,如print。一个native方法声明必须包含native修饰符。native修饰符暗示这个方法用另一种语言来实现。另外,这个native方法声明用一个封号结束,这个封号说明,这个方法的实现不是在类本身中。我们将在一个独立的Cfile 中实现print方法。
在nativeprint方法被调用前,实现native方法的native lib必须被加载。在这个例子中,我们将用HelloWorld类的staticinitializer来加载这个nativelib。在调用HelloWorld类的任何方法前,JVM都会自动运行natvieinitializer,这样做是为了确保在调用nativeprint方法前,nativelib 被加载。
我们定义main函数去运行Helloworld类。HelloWorld.main像调用常规函数一样调用printnative 方法。
带有库名的System.loadLibrary定位具有相同名的native库,并且把native库加载到app中。在这本书的后面我们将讨论准确的加载过程。现在我们只需要简单记忆下为了System.loadLibrary(“HelloWorld”)成功,我们需要创建一个名叫HelloWorld.dll或libHelloWorld.so的nativelib。
编译HelloWorld类
在你定义好HelloWorld类之后,把源文件保存到一个叫HelloWorld.java的文件中。接着用来自JDK2.0的javac编译源文件:
javacHelloWorld.java
这个命令将在当前目录生成一个名叫HelloWorld.class的文件。
2.4创建native方法头文件
下面,我们将用javah工具生成一个JNI风格的头文件,头文件在用C语言实现native方法时很有用。你可以在HelloWorld类所在目录运行javah,如下:
javah-jni HelloWorld
头文件的名字,是在类名后追加一个.h。上面的命令将生成一个HelloWorld.h的头文件。头文件最重要的部分是Java_HelloWorld_print的函数原型,那是一个需用用C语言实现的HelloWorld.print方法的声明:
JNIEXPORTvoid JNICALL Java_HelloWorld_print(JNIEnv *, jobject);
我们现在暂且忽略掉宏JNIEXPORT和JNICALL。你可能注意到了,C语言实现接受了两个参数,尽管对应的native方法声明并没有传递任何参数。对每一个native方法实现来说第一个参数是JNIEnv接口指针。第二个参数是HelloWorld类 自身的一个引用(就像C++中的“this”指针)。
我们将在这本书的后面讨论怎样使用JNIEnvinterface pointer 和 jobject arg,在这个简单的例子中暂且忽略两个参数。
写native实现方法
JNI头文件由javah命令生成,帮助你用C或C++写一个native实现方法。你写的这个函数必须和生成的头文件原型相对应。你可以用C语言在HelloWorld.c文件中实现HelloWorld.print方法,如下:
#include<jni.h>
#include<stdio.h>
#include “HelloWorld.h”
JNIEXPORTvoid JNICALL
Java_HelloWorld_print(JNIEnv*env,jobject obj)
{
printf(“HelloWorld!\n”);
return;
}
这个native方法的实现非常简单。它用printf()函数显示字符串“HelloWorld!”接着返回值。就像前面提到的,JNIEnv pinter 和object引用两个参数被忽略。
C程序包含三个头文件:
jni.h——这个头文件提供了调用JNIfunctions 的nativecode 信息。当写native方法时,在你的C或C++源文件中必须包含这个文件。
stdio.h——上面的代码片段中包含stdio.h,因为它使用printf函数。
HelloWorld.h——这个头文件是您用javah生成的。它包含用C/C++实现的Java_HelloWorld_print函数的函数原型。
2.6编译C源文件和创建Native库
记得在helloWorld.java file中创建HelloWorld类时包含了一行加载native库到program的代码:
System.loadLibrary(“HelloWorld”);
目前为止,所有需要的C代码都写完了,你需要编译HelloWorld.c和 生成nativelib。
不同的操作系统支持不同方式生成的nativelib。在 Solaris中,用下面的命令生成一个叫libHelloWorld.so共享库:
cc-G -I/java/include -I/java/include/solaris HelloWorld.c -olibHelloWold.so
-G选项告诉c编译器生成一个共享库代替常规的Solaris执行文件。在Win32中,用下面的命令在VC++编译器中生成一个DLLHelloWorld.dll:
cl-Ic:\java\include -Ic:\java\include\win32
-MD-LD HelloWorld.c -FeHelloWorld.dll
-MD选项确保HelloWorld.dll链接到Win32多线程C库中。-LD选项告诉编译器生成dll文件代替常规的Win32可执行文件。当然,Solaris和win32都需要输入你在自己机器中的路径。
运行程序
在此刻,你已经拥有了运行程序所需的两个组件。.clss文件(HelloWorld.class)调用一个native方法 ,而nativelib (libHelloWorld.so)实现了native方法。
由于HelloWorld类包含了它自己的main方法,又可以在Solaris或win32上用如下方法运行这个程序:
javaHelloWorld
你应当会看到下面的输出:
HelloWorld!
正确设置你的nativelib path 对于运行程序很重要。当加载nativelib时,native libpath 是一个供JVM查询的目录列表。如果你没有正确设置nativelib path,你将看到如下相似的错误提示:
java.lang.UnsatisfiedLinkError:no HelloWorld in library path
atjava.lang.Runtime.loadLibrary(Runtime.java)
atjava.lang.System.loadLibrary(System.java)
atHelloWorld.main(HelloWorld.java)
确保nativelib 驻留在 nativelib path 中目录之一。如果你运行在一个Solaris系统,LD_LIBTARY_PATH环境变量被用来定义nativelib path。确保它包含拥有libHelloWorld.so文件的目录。如果libHelloWorld.so文件在现在的目录中,你可以在标准shell和KornShell中使用下面两个命令建立LD_LIBRARY_PATH环境变量:
LD_LIBRARY_PATH= .
exportLD_LIBRARY_PATH
在Cshell中用下面的命令:
setenvLD_LIBRARY_PATH .
如果你是运行在Windows95 Winddows NT 上,确保HellWolrd.dll在当前目录,或者在目录列表PATH环境变量之下。
在JDK2中,你可以通过如下java命令行特画nativelib path作为一个系统属性:
java-Djava.library.path = . HelloWorld
“-D”命令行选项设置了一个Java平台的系统选项。设置java.library.path属性为“.”告知JVM在当前目录下查询。