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與系統中的原生方法交互的技術(在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

  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:\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命令行,如下圖所示:

  如果你電腦是32操作系統,就選用紅色方框進行編譯,如果64位操作系統選擇紅色方框下面那個進行編譯。具體操作如下:

  執行完上述命令后,在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 。運行結果如圖所示。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM