本文介绍了什么是native 关键字?为什么需要native关键字?以及如何使用native关键字?并通过一个Java与C语言混合编程,在Java语言中调用C语言生成的DLL文件,实现加法运算的小例子介绍Java语言与C语言混合编程的方法
一. 什么是 native Method
native Method 就是一个java调用非java代码的接口。一个 native Method
"A native method is a Java method whose implementation is provided by non-java code."
native Method 时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的(通常是C或C++等)。例如:
1 package java.lang;
2
3 public class Object {
4 ......
5 public final native Class<?> getClass();
6 public native int hashCode();
7 protected native Object clone() throws CloneNotSupportedException;
8 public final native void notify();
9 public final native void notifyAll();
10 public final native void wait(long timeout) throws InterruptedException;
11 ......
12 }
native 可以与所有其它的java标识符连用,但是abstract除外。这是合理的,因为 native 暗示这些方法是有实现体的,只不过这些实现体是非java的,但是abstract却显然的指明这些方法无实现体。 native 与其它java标识符连用时,其意义同非 native Method
native Method
native Method
final
本地方法非常有用,因为它有效地扩充了jvm,事实上,我们所写的java代码已经用到了本地方法,在sun的java的并发(多线程)的机制实现中,许多与操作系统的接触点都用到了本地方法,这使得java程序能够超越java运行时的界限。有了本地方法,java程序可以做任何应用层次的任务。
二. 为什么要使用 native Method
java使用起来非常方便,然而有些层次的任务用java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
1. 与java环境外交互:
有时java应用需要与java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节。
2. 与操作系统交互:
JVM支持着java语言本身和运行时库,它是java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一些底层(underneath在下面的)系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用C写的,还有,如果我们要使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
3. Sun's Java
Thread
三. JVM怎样使 native Method 跑起来
public之类)等等。
native, 这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用System.loadLibrary()实现的。
最后需要提示的是,使用本地方法是有开销的,它丧失了Java的很多好处。如果别无选择,我们可以选择使用本地方法。
native方法比作Java程序同C程序的接口,其实现步骤为:
- 在Java中声明 native方法,然后编译;
- 用javah产生一个.h文件;
- 写一个.cpp文件实现 native
- 将第三步的.cpp文件编译成动态链接库文件;
- 在Java中用 System.loadLibrary()方法加载第四步产生的动态链接库文件,这个 native方法就可以在Java中被访问了。(实际上已经在第1步的Java代码上提前写进去了)
四. Native关键字示例:Java调用C语言本地库
native
4.1 Java程序编写:
1 //fileName: TestAdd
2 public class TestAdd
3 {
4 public static void main(String[] args)
5 {
6 System.loadLibrary("NativeAdd");//加载由C编译器生成的DLL文件。
7
8 NativeAdd na = new NativeAdd();
9 System.out.println("3 + 4 = " + na.add(3, 4));
10 }
11 }
12
13 class NativeAdd
14 {
15 public native int add(int x, int y);
16 }
上面这段代码中 System.loadLibrary("NativeAdd"); 加载了动态类库,在Windows下加载的就是NativeAdd.dll,在Linux中加载的就是libNativeAdd.so,本文使用的Windows,所以后面使用NativeAdd.dll来表示NativeAdd动态链接库。注意不可以在代码中写上扩展名.dll或者.so,还要保证NativeAdd.dll在path路径中, 或者在与.class文件在同一个文件夹中,否则无法加载动态链接库。这个NativeAdd.dll是我们后面需要编译出来的东西.
javac TestAdd.java
4.2 使用javah生成头文件
javah NativeAdd 生成NativeAdd.h,注意此处不能写成 javah TestAdd ,因为我们使用 native 关键字修饰的方法在NativeAdd类中,而不是TestAdd类中。也就是说:native
执行完上面的命令之后,可以自动生成一个新文件:NativeAdd.h,代码如下:
1 /* DO NOT EDIT THIS FILE - it is machine generated */
2 #include <jni.h>
3 /* Header for class Add */
4
5 #ifndef _Included_Add
6 #define _Included_Add
7 #ifdef __cplusplus
8 extern "C" {
9 #endif
10 /*
11 * Class: Add
12 * Method: add
13 * Signature: (II)I
14 */
15 JNIEXPORT jint JNICALL Java_Add_add
16 (JNIEnv *, jobject, jint, jint);
17
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif
JNIEXPORT jint JNICALL Java_Add_add (JNIEnv *, jobject, jint, jint);
4.3 实现头文件中的函数
这里以C语言为例,介绍如何实现上面的函数,并生成DLL文件。
4.3.1 创建DLL工程
打开VC++6.0,执行:文件-->新建-->工程-->Win32 Dynamic-Link Library,输入工程名NativeAdd,选择工程路径到适当位置-->创建一个空的DLL工程。
4.3.2 创建.c文件
执行:文件-->新建-->文件-->C/C++Source File,输入文件名为NativeAdd.c,注意这里最好加上扩展名为.c,否则默认为.cpp文件,我们是使用C语言实现头文件中声明的函数,而非C++,为避免不必要的问题,最后还是加上扩展名.c,以免编译出错。
4.3.3 添加头文件
这里需要添加的头文件有三个,分别是:NativeAdd.h, jni.h, jni_md.h,其中NativeAdd.h是我们刚才使用javah命令生成的,jni.h在 \Java\jdk1.8.0_05\include ,jni.md_h在 \Java\jdk1.8.0_05\include\win32
4.3.4 编写C程序
代码如下:即将头文件的函数声明拷贝到c文件中,添加形参,编写函数体将函数实现即可。然后保存编译运行,即可在Debug文件夹下即生成NativeAdd.dll文件
1 #include <stdio.h>
2 #include "NativeAdd.h"
3
4 JNIEXPORT jint JNICALL Java_NativeAdd_add
5 (JNIEnv * env, jobject obj, jint x, jint y)
6 {
7 return x + y;
8 }
4.4 运行Java程序,调用DLL文件。
System.loadLibrary("NativeAdd");
java TestAdd
五. 使用批处理文件编译运行程序
批处理(Batch),也称为批处理脚本。批处理就是对某对象进行批量的处理,通常被认为是一种简化的脚本语言,它应用于DOS和Windows系统中。批处理文件的扩展名为.bat 。批处理文件的编写非常简单,只是相当于把DOS命令行的指令一条条的写到一个文本文件中,然后修改文本文件的扩展名为.bat。双击运行会自动用DOS窗口打开并依次执行批处理文件中的指令。
1 echo 清理以前生成的文件
2 del *.class
3 del *.dll
4 del *.exp
5 del *.lib
6 del *.obj
7 del *.h
8
9 pause
10
11 echo 编译java文件并生成.h文件
12 javac TestAdd.java
13 javah NativeAdd
14
15 pause
16
17 echo 生成.dll文件
18 cl -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -LD NativeAdd.c -FeNativeAdd.dll
19
20 echo 运行
21 java TestAdd
22
23 pause
文件名build&run.bat,用到了几个批处理命令,这里解释一下这个批处理文件的意思。
- echo 命令:将echo后面跟的内容输出到DOS窗口中,起到提示作用,相当于C中的printf。
- del
- pause
- cl 命令:VC编译器,在DOS下输入cl会打印相关信息。-I 指定头文件的搜索路径,注意路径要用"",这里指定的分别是jni.h和jni_md.h所在的路径。-LD 表示创建一个动态连接库。-Fe 设置最终可执行文件的存放路径及(或)文件名。
有个这个批处理文件,我们就不用使用VC创建工程的方法去生成dll文件了,而是直接使用批处理文件中的cl命令。只需要将build&run.bat,TestAdd.java,NativeAdd.c这三个文件放到同一个文件夹中,双击运行bat文件,批处理文件会完成上面的命令。
参考: Java的native关键字