Java native方法、JNI实例及常见错误分析

时间:2021-11-10 16:03:16

1.概述

  今天在看java关于调用本机代码子程序来获得较快的执行时间,或者,你希望用一个专用的第三方的库,例如统计学包。然而,因为Java程序被编译为字节码,字节码有Java运行时系统解释(或动态编译),看起来在Java程序中调用本机代码子程序是不可能。幸运的是,这个结论是错误的。Java提供了native关键字,该关键字用来声明本机代码方法。一旦声明,这些方法可以在Java程序中被调用,就像调用其他Java方法一样。

2.native关键字用法

  既然Java提供了native方法,那么如何实现呢?native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由Java调用。这些函数的实现体在DLL中,JDK的源码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。总而言之:

  1. native是用做java和其他语言(如C++)进行协作时使用,也就是native后的函数的实现不是用java写的。
  2. 既然都不是java,那就别管它的源代码了,我们只需要知道这个方法已经被实现即可。
  3. native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的,java只能调用。
  4. java是跨平台的语言,既然是跨平台,所付出的代价就是牺牲对底层的控制,而java要实现对底层的控制,就需要一些语言的帮助,这个就是native的作用了。

3.JNI简介

  native方法是通过java中的JNI实现的。JNI是Java Native Interface的缩写。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受到支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java虚拟机实现下。

  目前java与dll交互的技术主要有三种:jni、jawin和jacob。JNI(Java Native Interface)是sun提供的java与系统中的原生方法交互的技术(在windows\linux系统中,实现java与native method互调)。目前只能由C/C++实现。后两个都是sourceforge上的开源项目,同时也都是基于JNI技术的windows系统上的应用库。Jacob(Java-ComBridge)提供了java程序调用microsoft的com对象中的方法的能力。而除了com对象外,jawin(Java/Win32 integration project)还可以win32-dll动态链接库中的方法。就功能而言:JIN>>jawin>jacob.就易用性而言,正好相反:jacob>jawin>>JNI

Java native方法、JNI实例及常见错误分析

  JVM封装了各种操作系统实际的差异性的同时,提供了JNI技术,使得开发者可以通过java程序(代码)调用到操作系统相关的技术实现的函数,从而与其他技术和系统交互,使用其他技术实现的功能;同时其他技术和系统也可以通过jni提供的相应原生接口调用java应用系统内部实现的功能。

  在windows系统上,一般可执行的应用程序都是基于native的PE结构,windows上的jvm也是基于native结构实现的。Java应用体系都是构建与jvm之上。

Java native方法、JNI实例及常见错误分析

  JNI对于应用本身来说,可以看做一个代理模式。对于开发者来说,需要使用C/C++来实现一个的代理程序(jni程序)来实际操作目标原生函数,java程序中则是JVM通过加载并调用此JNI程序来间接地调用目标原生函数。

Java native方法、JNI实例及常见错误分析

4.JNI的书写步骤

  1. 编写带有native声明的方法的java类,生成java文件
  2. 使用javac命令编译所编写的java类,生成.class文件
  3. 使用javah -jni java类名生成扩展名为h的头文件,也即为生成.h文件。
  4. 使用C/C++(或者其他编程语言)实现本地方法,创建.h文件的实现,也就是创建.cpp文件实现.h文件实现的.h文件中的方法。
  5. 将C/C++编写的文件生成动态链接库,生成dll文件

5.JNI实例

  下列是所有操作都在目录:D:\Native下进行的,这样做的好处是便于控制。还有另外一个要求是我们的java类不含包名。不然在使用javah -jni class文件名会提示错误:找不到‘class’的类文件。

  1. 编写带有native声明的方法的java类:NativeDemo.java.
    public class NativeDemo {
    int i;
    public static void main(String[] args) {
    NativeDemo ob
    =new NativeDemo();

    ob.i
    =10;
    System.out.println(
    "This is ob.i before the native method:"+
    ob.i);

    ob.test();
    //call a native method.
    System.out.println("This is ob.i after the native method:"+
    ob.i);
    }

    //declare native method
    public native void test();

    //load DLL that contains static method
    static{
    System.loadLibrary(
    "NativeDemo");
    }


    }

    在编写的Java类时候,一定要注意不要带包名。尤其用eclipse时候自动添加包名。

  2. 使用javac命令编译所编写的java类:D:\Native>javac NativeDemo.java。执行完上述命令以后生成D:\Native\NativeDemo.class
  3. 使用javah -jni java类名生成扩展名为h的头文件:D:\Native>javah -jni NativeDemo。执行完上述命令以后生成D:\Native\NativeDemo.h文件,该文件内容如下:
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include
    <jni.h>
    /* Header for class NativeDemo */

    #ifndef _Included_NativeDemo
    #define _Included_NativeDemo
    #ifdef __cplusplus
    extern
    "C" {
    #endif
    /*
    * Class: NativeDemo
    * Method: test
    * Signature: ()V
    */
    JNIEXPORT
    void JNICALL Java_NativeDemo_test
    (JNIEnv
    *, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

    这个h文件相当于我们在java里面的接口,这里声明了一个Java_NativeDemo_test(JNIEnv *, jobject).方法,然后在我们的     本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里一致。

  4. 使用C/C++实现本地方法:创建NativeDemo.c,代码如下所示:
    *This file contains the C version of the test() method*/
    #include
    <jni.h>
    #include
    "NativeDemo.h"
    #include
    <stdio.h>

    JNIEXPORT
    void JNICALL Java_NativeDemo_test(JNIEnv *env,jobject obj)
    {
    jclass cls;
    jfieldID fid;
    jint i;

    printf(
    "Starting the native method.\n");
    cls
    =(*env)->GetObjectClass(env, obj);
    fid
    =(*env)->GetFieldID(env, cls, "i","I");

    if(fid==0){
    printf(
    "Could not get field id.\n");
    return;
    }

    i
    =(*env)->GetIntField(env ,obj,fid);
    printf(
    "i=%d\n",i);
    (
    *env)->SetIntField(env,obj,fid,2*i);
    printf(
    "Ending the native method.\n");

    }


     

  5. 将C/C++编写的文件生成动态连接库:将D:\Program Files\Java\jdk1.7.0_07\include\jni.h和D:\Program Files\Java\jdk1.7.0_07\include\win32\jni_md.h这个两个文件拷贝到D:\Native\目录下。
  6. 执行cl/LD: D:\Native\NativeDemo.c得到NativeDemo.dll文件。这里要用到visual studio 2010,要使用其中的cl命令,必须打开visual studio命令行,如下图所示:

Java native方法、JNI实例及常见错误分析  如果你电脑是32操作系统,就选用红色方框进行编译,如果64位操作系统选择红色方框下面那个进行编译。具体操作如下:Java native方法、JNI实例及常见错误分析

  执行完上述命令后,在D:\Program Files\Microsoft Visual Studio 10.0\VC可以看到生成的四个文件,分别是:

  • NativeDemo.dll
  • NativeDemo.exp
  • NativeDemo.lib
  • NativeDemo.obj

  将其中的NativeDemo.dll拷贝到D:\Native\目录下。

  7.执行class得到结果

  在cmd中运行:在D:\Native\目录下:java NativeDemo 。运行结果如图所示。

Java native方法、JNI实例及常见错误分析

7.注意事项:

  1. java源文件不要带有包名。我测试没有包名可以顺利生成h文件。java源文件带有包名在生成h文件时候,提示错误找不到“class类名”的类文件。
  2. 在将jni.h和jni_md.h拷贝到D:\Native\目录下,编译时候出现错误:fatal error C1083:Cannot open include file:'jni.h'。这时候解决办法是:将D:\Program Files\Java\jdk1.7.0_07\include\jni.h、D:\Program Files\Java\jdk1.7.0_07\include\win32\jawt_md.h、D:\Program Files\Java\jdk1.7.0_07\include\win32\jni_md.h拷贝到D:\Program Files\Microsoft Visual Studio 10.0\VC\include\目录下就可以完美解决这个问题。
  3. 在使用visual studio 2010命令提示符时候选择相对应的32/64操作系统下进行编译。否则会出现报错。

8.参考资料:

  http://blog.csdn.net/xw13106209/article/details/6989415