JNIHelloWorld

文章内容如题,只是对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

你可能感兴趣的:(java,C++,c,C#,jni)