Java语⾔与C语⾔混合编程(1)--Javanative关键字
⼀. 什么是 native Method
  简单地讲,⼀个native Method就是⼀个java调⽤⾮java代码的接⼝。⼀个native Method是这样⼀个java的⽅法:该⽅法的实现由⾮java语⾔实现,⽐如C。这个特征并⾮java所特有,很多其它的编程语⾔都有这⼀机制,⽐如在C++中,你可以⽤extern "C"告知C++编译器去调⽤⼀个C的函数。
  "A native method is a Java method whose implementation is provided by non-java code."
  在定义⼀个native Method时,并不提供实现体(有些像定义⼀个java interface),因为其实现体是由⾮java语⾔在外⾯实现的(通常是C或C++等)。例如:
1package java.lang;
2
3public class Object {
4    ......
5public final native Class<?> getClass();
6public native int hashCode();
7protected native Object clone() throws CloneNotSupportedException;
8public final native void notify();
9public final native void notifyAll();
10public final native void wait(long timeout) throws InterruptedException;java爱心代码编程简单
11    ......
12 }
  标识符native可以与所有其它的java标识符连⽤,但是abstract除外。这是合理的,因为native暗⽰这些⽅法是有实现体的,只不过这些实现体是⾮java的,但是abstract却显然的指明这些⽅法⽆实现体。native与其它java标识符连⽤时,其意义同⾮native Method并⽆差别。
  ⼀个native Method⽅法可以返回任何java类型,包括⾮基本类型,⽽且同样可以进⾏异常控制。这些
⽅法的实现体可以⾃制⼀个异常并且将其抛出,这⼀点与java的⽅法⾮常相似。
  native Method的存在并不会对其他类调⽤这些本地⽅法产⽣任何影响,实际上调⽤这些⽅法的其他类甚⾄不知道它所调⽤的是⼀个本地⽅法。JVM将控制调⽤本地⽅法的所有细节。
  如果⼀个含有本地⽅法的类被继承,⼦类会继承这个本地⽅法并且可以⽤java语⾔重写这个⽅法(这个似乎看起来有些奇怪),同样的如果⼀个本地⽅法被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
  Sun的解释器是⽤C实现的,这使得它能像⼀些普通的C⼀样与外部交互。jre⼤部分是⽤java实现的,它也通过⼀些本地⽅法与外界交互。例如:类java.lang.Thread的 setPriority()⽅法是⽤java实现的,但是它实现调⽤的是该类⾥的本地⽅法setPriority0()。这个本地⽅法是⽤C实现的,并被植⼊JVM内部,在Windows 95的平台上,这个本地⽅法最终将调⽤Win32 SetPriority() API。这是⼀个本地⽅法的具体实现由JVM直接提供,更多的情况是本地⽅法由外部的动态链接库(external dynamic link library)提供,然后被JVM调⽤。
三. JVM怎样使 native Method 跑起来
  我们知道,当⼀个类第⼀次被使⽤到时,这个类的字节码会被加载到内存,并且只会回载⼀次。在这个被加载的字节码的⼊⼝维持着⼀个该类所有⽅法描述符的list,这些⽅法描述符包含这样⼀些信息:⽅法代码存于何处,它有哪些参数,⽅法的描述符(public之类)等等。
  如果⼀个⽅法描述符内有native, 这个描述符块将有⼀个指向该⽅法的实现的指针。这些实现在⼀些DLL⽂件内,但是它们会被操作系统加载到java程序的地址空间。当⼀个带有本地⽅法的类被加载时,其相关的DLL并未被加载,因此指向⽅法实现的指针并不会被设置。当本地⽅法被调⽤之前,这些DLL才会被加载,这是通过调⽤System.loadLibrary()实现的。
  最后需要提⽰的是,使⽤本地⽅法是有开销的,它丧失了Java的很多好处。如果别⽆选择,我们可以选择使⽤本地⽅法。
  可以将native⽅法⽐作Java程序同C程序的接⼝,其实现步骤为:
1. 在Java中声明native⽅法,然后编译;
2. ⽤javah产⽣⼀个.h⽂件;
3. 写⼀个.cpp⽂件实现native导出⽅法,其中需要包含第⼆步产⽣的.h⽂件(注意其中⼜包含了JDK带的jni.h⽂件);
4. 将第三步的.cpp⽂件编译成动态链接库⽂件;
5. 在Java中⽤System.loadLibrary()⽅法加载第四步产⽣的动态链接库⽂件,这个native⽅法就可以在Java中被访问了。(实际上已经在
第1步的Java代码上提前写进去了)
四. Native关键字⽰例:Java调⽤C语⾔本地库
  代码功能:Java与C语⾔混合编程,实现两个数字相加。即在Java的类中声明native⽅法,⽽具体实现由C语⾔完成。
4.1  Java程序编写:
1//fileName: TestAdd
2public class TestAdd
3 {
4public static void main(String[] args)
5    {
6        System.loadLibrary("NativeAdd");//加载由C编译器⽣成的DLL⽂件。
7
8        NativeAdd na = new NativeAdd();
9System.out.println("3 + 4 = " + na.add(3, 4));
10    }
11 }
12
13class NativeAdd
14 {
15public 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 可以看到⽣成⽂件TestAdd.class⽂件和NativeAdd.class⽂件,不过现在还不能运⾏TestAdd.class,因为我们还没有⽣成NativeAdd.dll,下⾯介绍如何⽣成NativeAdd.dll⽂件。
4.2  使⽤javah⽣成头⽂件
  使⽤命令⾏ javah NativeAdd ⽣成NativeAdd.h,注意此处不能写成 javah TestAdd ,因为我们使⽤native关键字修饰的⽅法在NativeAdd类中,⽽不是TestAdd类中。也就是说:native关键字修饰的⽅法
在那个类中,就使⽤javah命令⽣成对应的头⽂件,然后使⽤C 语⾔实现这个⽅法。
  执⾏完上⾯的命令之后,可以⾃动⽣成⼀个新⽂件: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
8extern"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); ,这个头⽂件我们不⽤管也不⽤做任何修改,他是有JNI(Java Native Interface)⾃动⽣成的,我们要做的就是编写这个函数的函数体。
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 。将这两个⽂件拷贝到⼯程⽂件夹中。右击Header File-->添加⽂件到⽬录,选择我们刚刚拷贝进来的三个头⽂件NativeAdd.h, jni.h, jni_md.h。
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 {
7return x + y;
8 }
4.4  运⾏Java程序,调⽤DLL⽂件。
  将上⼀步骤中的NativeAdd.dll⽂件拷贝到TestAdd.class所在的⽂件中。注意dll的⽂件名要与Java代码
中System.loadLibrary("NativeAdd"); 保持⼀致。如果不⼀致可以⾃⼰修改⽂件名。
  执⾏命令号 java TestAdd 即可看到运⾏输出结果为7,即:3 + 4 = 7。
五. 使⽤批处理⽂件编译运⾏程序
  (Batch),也称为批处理脚本。批处理就是对某对象进⾏批量的处理,通常被认为是⼀种简化的脚本语⾔,它应⽤于DOS和Windows系统中。批处理⽂件的扩展名为.bat 。批处理⽂件的编写⾮常简单,只是相当于把DOS命令⾏的指令⼀条条的写到⼀个⽂本⽂件中,然后修改⽂本⽂件的扩展名为.bat。双击运⾏会⾃动⽤DOS窗⼝打开并依次执⾏批处理⽂件中的指令。
1echo清理以前⽣成的⽂件
2del *.class
3del *.dll
4del *.exp
5del *.lib
6del *.obj
7del *.h
8
9pause
10
11echo编译java⽂件并⽣成.h⽂件
12 javac TestAdd.java
13 javah NativeAdd
14
15pause
16
17echo⽣成.dll⽂件
18 cl -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -LD NativeAdd.c -FeNativeAdd.dll
19
20echo运⾏
21 java TestAdd
22
23pause
  ⽂件名build&run.bat,⽤到了⼏个批处理命令,这⾥解释⼀下这个批处理⽂件的意思。
echo  命令:将echo后⾯跟的内容输出到DOS窗⼝中,起到提⽰作⽤,相当于C中的printf。
del  命令:删除⽂件,*.class表⽰删除所有的class⽂件,在编译运⾏前我们先删除上次编译⽣成的⽂件,以免对本次编译运⾏结果的产⽣⼲扰。
pause命令:批处理暂停,在屏幕输出"按任意键继续...",按任意键可以继续执⾏批处理⽂件。
cl    命令:VC编译器,在DOS下输⼊cl会打印相关信息。-I 指定头⽂件的搜索路径,注意路径要⽤"",这⾥指定的分别是jni.h和jni_md.h所在的路径。-LD 表⽰创建⼀个动态连接库。-Fe 设置最终可执⾏⽂件的存放路径及(或)⽂件名。
  有个这个批处理⽂件,我们就不⽤使⽤VC创建⼯程的⽅法去⽣成dll⽂件了,⽽是直接使⽤批处理⽂件中的cl命令。只需要将
build&run.bat,TestAdd.java,NativeAdd.c这三个⽂件放到同⼀个⽂件夹中,双击运⾏bat⽂件,批处理⽂件会完成上⾯的命令。
参考:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。