Step By Step(Java 本地方法篇) (一)

2014-11-24 03:21:35 · 作者: · 浏览: 8

本篇是该系列的最后一篇文章,就让我们以JNI作为结束吧!

众所周知,使用多种语言协同开发时,经常会导致一些未知问题的发生,也会给我们程序的调试带来一定的负担。鉴于此,我们也只有在充分了解到必要性之后再决定用C/C++来替换部分Java代码,以达到我们预期的目的,考虑使用本地代码主要有以下三个理由:

1) 你的应用需要访问系统的各个特性和设备,这些特性和设备通过Java平台是无法访问的;

2) 你已经有了大量的测试过和调试过得用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上;

3) 通过基准测试,你已经发现所编写的Java代码比用其他语言编写的等价代码要慢得多。

在Java中提供了用于和本地C语言代码进行互操作的API,称为Java本地接口(JNI)。

1. 第一个本地方法:

1) 声明一个含有本地方法的类:

1 public class HelloNative {

2 public static native void greeting();

3 }

注意以上代码中的native关键字,以及在函数声明的末尾直接用分号结束该函数的声明。

2) 将该.java文件编译成.class文件,如果使用eclipse直接用IDE编译即可,否则可以使用命令行:

javac HelloNative.java

将会发现在当前目录下生成了相应的HelloNative.class文件。

3) 通过%JAVA_HOME%/bin/javah命令在命令行窗口为HelloNative.class文件生成相应的C语言使用的头文件。

javah HelloNative #这里不要添加.class后缀,执行目录为HelloNative.class所在的目录。

需要注意的是,在我们的例子中并没有给HelloNative方法添加包名,如:

1 package mytest;

2 public class HelloNative {

3 public static native void greeting();

4 }

针对该种情况在基于.class文件生成C语言要求的头文件时,需要做出以下调整:

javah mytest.HelloNative #这里在类的前面添加了包名mytest,包名和类名之间使用dot作为连接符。还需要注意的是执行当前命令的目录为HelloNative.class所在目录的上一级目录,如果包的名字为package mytest.first; 则执行目录为HelloNative.class的上两级目录,以此类推。执行成功后将会在当前执行目录下生成一个名为mytest_HelloNative.h(针对包名为mytest的case)。该文件中包含了和HelloNative.greeting()对应的C语言导出函数的声明。为了实现本地代码,要求生成的C函数名符合Java在JNI中定义的命名规则,如:

1) 使用完整的Java类名.方法名,如:HelloNative.greeting。如果类属于某个包,那么在前面添加包名,如mytest.HelloNative.greeting;

2) 用下划线替换掉所有的dot,并加上Java_前缀,如:Java_mytest_HelloNative_greeting;

3) 如果你重载本地方法,既使用相同名字提供多个本地方法,那么必须在名称后附加两个下划线,后面再加上已编码的参数类型。如:Java_mytest_HelloNative_greeting__表示当前没有参数的greeting,如果在Java中同时定义了另一个greeting(int repeat),其对应的C函数声明为Java_mytest_HelloNative_greeting__I,这里的I表示参数类型,更为具体的描述,后面会逐步给出。

以下为该头文件的代码:

1 /* DO NOT EDIT THIS FILE - it is machine generated */

2 #include

3 /* Header for class mytest_HelloNative */

4 #ifndef _Included_mytest_HelloNative

5 #define _Included_mytest_HelloNative

6 #ifdef __cplusplus

7 extern "C" {

8 #endif

9 /*

10 * Class: mytest_HelloNative

11 * Method: greeting

12 * Signature: ()V

13 */

14 JNIEXPORT void JNICALL Java_mytest_HelloNative_greeting

15 (JNIEnv *, jclass);

16

17 #ifdef __cplusplus

18 }

19 #endif

20 #endif

我们后面需要做的是为该头文件创建一个对应的HelloNative.c文件包含导出函数的实现代码,如:

1 #include "HelloNative.h"

2 #include

3 JNIEXPORT void JNICALL Java_mytest_HelloNative_greeting(JNIEnv* env, jclass cl) {

4 printf("Hello Native World!\n");

5 }

注意如果使用了C++的代码,由于Java并未提供和C++的交互能力,因此需要在该函数前加extern "C"来表示按照C语言的方法导出,以阻止编译器生成C++特有的代码,如:

1 extern "C"

2 JNIEXPORT void JNICALL Java_mytest_HelloNative_greeting(JNIEnv* env, jclass cl) {

3 cout << "Hello, Native World!" << endl;

4 }

我们下一步需要做的是利用平台已有的编译工具在命令行窗口编译该文件,如:

Linux:gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so HelloNative.c

Windows: c1 -I jdk/include -I jdk/include/win32 -LD HelloNative.c -FeHelloNative.dll (也可以通过VC的IDE完成)

生成动态库文件之后,需要将其拷贝到path(win32)或LD_LIBRARY_PATH(Linux)环境变量包含的目录下,以便于执行时操作系统可以定位到该动态库,也可以将当前动态库所在的目录添加到path(win32)或LD_LIBR