文章内容如题,只是对JNI使用的简单介绍。相信有许多同行也跟我一样在这方面知之甚少,所有在这里也总结一下,希望方便大家学习。
1.简介
JNI是Java Native Interface的缩写,它的设计目的是:
The standard Java class library may not support the platform-dependent features needed by your application.
You may already have a library or application written in another programming language and you wish to make it accessible to Java applications.
You may want to implement a small portion of time-critical code in a lower-level programming language, such as assembly, and then have your Java application call these functions
2.JNI的书写步骤
1)编写带有native声明方法的java类。
2)使用javac命令编译所编写的java类。
3)使用编译后的.class文件,运行javah -jni 类名 命令生成扩展名为.h的头文件。
4)使用C/C++实现本地方法。
也就是自定义native方法所调用的其他语言的文件,该文件中实现java中声明的native方法(本文以c语言为例,所以建立文件为.c扩展名)。
5)使用C/C++编写的文件(上述过程生成的.c文件)生成动态链接库文件。
即使用cl.exe命令
以上诸步骤我的理解总结如下:在java文件中如果想调用c的方法,需要通过声明的native方法来调用,调用什么方法呢,按照面向接口编程的思想需要事先定义一个供二者通信的接口吧,于是乎.h文件就出现了,java端通过javah命令生成这个接口文件,文件中声明了那些native方法,但是仅仅是定义一些基本信息(毕竟是接口嘛)比如:方法名、返回值、参数等。接口有了,想使用的话就需要对他进行实现了,这时就需要使用native语言(本例为C)对接口中方法进行实现了,这时就出现了步骤4中的.c扩展名的文件。文件有了还不行,需要对其进行转换,生成一个.dll文件,才能被java所使用,到了这一步大功告成。
下面就通过一个简单的helloworld实例演示上面的步骤:
1)编写java程序:
public class HelloWorld { //Load动态库:System.loadLibrary("hello");加载动态库(我们可以这样理解:我们的方法displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“hello”是动态库的名字。 static { System.loadLibrary("hello"); } //声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。 public native void displayHelloWorld(); //测试 public static void main(String[] args){ new HelloWorld().displayHelloWorld(); } }
2)编译成.class文件
javac HelloWorld.java
3)生成扩展名为.h的头文件
javah -jni HelloWorld
生成的头文件的内容:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: displayHelloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致。如果需要在包深层次的某个class文件进行.h头文件的生成。则需要进行如下步骤:
·通过命令行进入到工程bin文件夹中
·输入命令:javah -classpath . packagepath.类名
以上命令将产生一个名为:packagepath_类名.h 的头文件(其中packagepath路径中.被替换成_出现在文件名中)
需要注意:方法名中一定要正确指向其实现的方法所在的.h文件,不然虽然能正确进行下面步骤,但是运行时却出现异常。例如:生成的.h文件名为:com_mazhj_HelloWorld.h,则.c文件中相应的实现方法名应该为:Java_com_mazhj_HelloWorld_displayHelloWorld(JNIEnv *, jobject)
4)编写本地方法
实现和由javah命令生成的头文件里面声明的方法名相同的方法。
#include "jni.h" #include "HelloWorld.h" JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj) { printf("Hello world!\n"); return; }
注意代码中的第1行,需要将jni.h文件引入,(可以在%JAVA_HOME%/include文件夹中找到)因为在程序中的JNIEnv、jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(以便将HelloWorld.h头文件里面声明的方法加以实现)。最后将上面代码保存为HelloWorldImpl.c文件。
5)生成动态库文件
以window平台为例,需要生成dll文件。在保存的HelloWorldImpl.c文件夹下面,使用VC的编译器cl.exe工具生成。
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD HelloWorldImp.c -Fehello.dll
下面对该命令进行一下简单的介绍:
·CL.exe 是控制 Microsoft C 和 C++ 编译器与链接器的 32 位工具。编译器产生通用对象文件格式 (COFF) 对象 (.obj) 文件。链接器产生可执行文件 (.exe) 或动态链接库文件 (DLL)。
·参数-I:在目录中搜索包含文件。
参数-LD:创建动态链接库。
参数-Fe:重命名可执行文件
注意:生成的dll文件名在选项-Fe后面进行配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32参数加上,是因为在第4步里面编写本地方法的时候引入了jni.h文件。
6)运行程序
java HelloWorld
注意:生成的.dll文件需要放在系统path中,这样,Java进程在运行中才能找到本地库并动态加载。我们可以通过环境变量System.getProperty("java.library.path")来查看当前JVM搜索本地库的路径,这时,就会遇到一个问题,部署应用的时候要记住将本地库拷贝到环境变量path指定的路径中。一般在windows平台上直接copy到C:\WINDOWS\System32目录下了事(此方法好使)。但要换一台机器部署怎么办?除了要把Java程序拿过去,还要记的把本地库也copy到正确的目录,真麻烦。于是想看看有什么好办法来解决这个问题。首先,最容易想到的是,把本地库和class文件放在一起,利用Class.getResource(str)找到路径,然后加到环境java.library.path中:
URL url = Foo.class.getResource("Foo.class"); String path = (new File(url.getPath())).getParent(); System.setProperty("java.library.path", path);
看上去很好,但却不能工作。查了一下ClassLoader的源代码,原来它把搜索路径定义为静态变量并只初始化一次,后面再设置java.library.path就没有用了。 ClassLoader代码片断:
// The paths searched for libraries static private String usr_paths[]; static private String sys_paths[]; ... if (sys_paths == null) { usr_paths = initializePath("java.library.path"); sys_paths = initializePath("sun.boot.library.path"); }
正在一筹莫展是,翻看JACOB的源代码,忽然有了惊喜的发现。
try { //Finds a stream to the dll. Change path/class if necessary InputStream inputStream = getClass().getResource("/jacob.dll").openStream(); //Change name if necessary File temporaryDll = File.createTempFile("jacob", ".dll"); FileOutputStream outputStream = new FileOutputStream(temporaryDll); byte[] array = new byte[8192]; for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) { outputStream.write(array, 0, i); } outputStream.close(); temporaryDll.deleteOnExit(); System.load(temporaryDll.getPath()); return true; } catch(Throwable e) { e.printStackTrace(); return false; }
高,真是好办法。把dll放在classpath中,用Class.getResource(str).openStream()读取这个dll,然后写到temp目录中,最后用System.load(path)来动态加载。多说一句,为什么得到了jacob.dll的URL不直接去加载呢?想想看,如果把dll和class一起打成Jar包,ClassLoader还是不能加载本地库,因为System.load(path)需要的是dll的完整路径,但并不支持jar协议。还不明白,看看下面的代码:
URL url = Foo.class.getResource("/java/lang/String.class"); System.out.println(url.toExternalForm()); System.out.println(url.getFile());
运行结果:
jar:file:/C:/Program%20Files/Java/jdk1.6.0_21/jre/lib/rt.jar!/java/lang/String.class file:/C:/Program%20Files/Java/jdk1.6.0_21/jre/lib/rt.jar!/java/lang/String.class
ClassLoader中用new File(name),当然会找不到文件。同时,看看我的第一种方法,就算能设置成功环境java.library.path,如果dll是在jar包中,还是加载不了。
附:VC编译器下载地址:http://www.oamo.com/Software/Catalog130/1569.html
发表评论
-
jacob莫名奇妙抛异常
2010-11-01 18:17 992最近使用jacob写word文件的时候发现个奇怪的现象:写入数 ... -
正则表达式匹配换行
2010-10-19 19:19 3188因为早期使用正则表达式的工具是基于行的。它们都是一行一行的读入 ... -
volatile关键字一
2010-10-04 10:39 813转自IBM论坛文章,原题目:java理论与实践:正确使用vol ... -
volatile关键字二
2010-10-04 10:35 884java中int等不大于32位的类型上的简单操作都是原子操作, ... -
终结函数守卫者
2010-09-26 21:58 17021.引入:之所以要使用 ... -
System.getProperty()
2010-09-18 14:45 950java.version Java 运行时环境版本 java. ... -
重载(overload)与覆盖(override)
2010-08-01 13:03 799“重载(overload)”: 1、overload时,方法 ... -
用循环代替递归
2010-07-31 23:18 3225public class nhn { public s ...
相关推荐
创建一个名为`hello.c`(或`hello.cpp`)的文件,包含`JNIHelloWorld.h`并实现`Java_JNIHelloWorld_sayHello`函数: ```c #include #include "JNIHelloWorld.h" JNIEXPORT void JNICALL Java_JNIHelloWorld_...
本教程将带你一步步了解如何使用JNI技术,通过两个示例项目"Java-JNIHelloWorld"和"C++ -JNIHelloWorld"来深入学习这一主题。 首先,我们需要理解JNI的基本概念。JNI是一个接口,提供了在Java虚拟机(JVM)中调用...
在"jni"目录下,你会看到一个名为"JniHelloWorld"的C/C++源文件,通常命名为"main.c"或"hello-jni.c"。这个文件包含了简单的"Hello World"实现,例如: ```c #include #include #define LOG_TAG "HelloJNI" #...
例如,一个简单的JNIHelloWorld示例可能如下: ```c // hello_jni.h #include #ifndef _Included_hello_jni_H #define _Included_hello_jni_H JNIEXPORT void JNICALL Java_...
私有原生String jniHelloWorld(); 使用javah生成C ++头 cd projectPath / app / src / main javah -d jni -cp pathTo / Library / Android / sdk / platforms / android-22 / android.jar:../../ build / ...