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

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与系统中的原生方法交互的技术(在windowslinux系统中,实现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

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

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

  JNI对于应用本身来说,可以看做一个代理模式。对于开发者来说,需要使用C/C++来实现一个的代理程序(jni程序)来实际操作目标原生函数,java程序中则是JVM通过加载并调用此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:NativeNativeDemo.class
  3. 使用javah -jni java类名生成扩展名为h的头文件:D:Native>javah -jni NativeDemo。执行完上述命令以后生成D:NativeNativeDemo.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.
    ");
        cls=(*env)->GetObjectClass(env, obj);
        fid=(*env)->GetFieldID(env, cls, "i","I");
        
        if(fid==0){
            printf("Could not get field id.
    ");
            return;
        }
        
        i=(*env)->GetIntField(env ,obj,fid);
        printf("i=%d
    ",i);
        (*env)->SetIntField(env,obj,fid,2*i);
        printf("Ending the native method.
    ");
        
    }


     

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

  如果你电脑是32操作系统,就选用红色方框进行编译,如果64位操作系统选择红色方框下面那个进行编译。具体操作如下:

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

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

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

  7.执行class得到结果

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

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 FilesJavajdk1.7.0_07includejni.h、D:Program FilesJavajdk1.7.0_07includewin32jawt_md.h、D:Program FilesJavajdk1.7.0_07includewin32jni_md.h拷贝到D:Program FilesMicrosoft Visual Studio 10.0VCinclude目录下就可以完美解决这个问题。
  3. 在使用visual studio 2010命令提示符时候选择相对应的32/64操作系统下进行编译。否则会出现报错。

8.参考资料:

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

原文地址:https://www.cnblogs.com/chuji1988/p/4005736.html