Java編程--關於JNI你應該知道的一切,Java編程--JNI

來源:互聯網
上載者:User

Java編程--關於JNI你應該知道的一切,Java編程--JNI

出於效率的問題,很多情況下,我們需要在上層的Java代碼中調用底層 C或C++實現,這時jni就可以大顯身手了。jni(Java Native Interface)允許Java代碼和其他語言寫的代碼進行互動,使用java與本地已編譯的代碼互動,通常會喪失平台可移植性。但是,有些情況下這樣 做是可以接受的,甚至是必須的。例如,使用一些舊的庫,與硬體、作業系統進行互動,或者為了提高程式的效能。

使用JNI編程的步驟

關於javah:在命令視窗中運行javah -h 命令我們可以看到javah的所有選項,這裡簡單介紹一下:

  • -classpath,用於裝入類的路徑
  • -d,輸出目錄
  • -o,輸出檔案, -d和-o只能使用其中之一
  • -jni,產生jni樣式的標頭檔(預設)
編寫第一個JNI程式

看到了上面的步驟我們來親自實踐一下,利用jni編寫一個簡單的hello world程式。
1.首先,我們用Java代碼編寫一個本地方法hello:

package com.example.jnitest;public class JniTest {    public static native void hello();}

2.接下來我們利用javah產生標頭檔,需要注意的是我們應該首先編譯該工程得到.class檔案。然後我們運行命令列來到工程目錄/bin目錄 下,輸入命令 javah -classpath . com.example.jnitest.JniTest 得到標頭檔,

得到標頭檔的代碼如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_jnitest_JniTest */#ifndef _Included_com_example_jnitest_JniTest#define _Included_com_example_jnitest_JniTest#ifdef __cplusplusextern "C" {#endif/* * Class:     com_example_jnitest_JniTest * Method:    hello * Signature: ()V */JNIEXPORT void JNICALL Java_com_example_jnitest_JniTest_hello  (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif

3.接下來我們要用C語言實現我們的程式,為了方便建立共用庫檔案,在這裡我在VS2012中建立了一個控制台工程,並在應用程式設定中選擇dll和空項目。

接著將剛剛得到的標頭檔添加進來並建立一個cpp檔案來實現我們的程式。

#include"jni_hello.h"JNIEXPORT void JNICALL Java_com_example_jnitest_JniTest_hello    (JNIEnv *, jclass){        printf("Hello Jni!\n");}

注意問題,如果我們直接將標頭檔添加進來可能會找不到標頭檔jni.h,這時我們需要配置一下我們的工程,右鍵 工程–>屬性–>VC++目錄,選擇包含目錄–>編輯,將jni標頭檔的目錄添加進來,一般需要添加的目錄是jdk目錄下的 include檔案夾以及include/win32檔案夾,

4.產生共用庫檔案,接下來我們只需要運行該工程即可在Debug檔案夾下找到.dll檔案,需要注意的是,vs直接產生的是32位的dll檔案,如果你 的機器是64位就會報錯Can’t load IA 32-bit .dll on a AMD 64-bit platform,這時我們就用配置一下自己的VS工程:產生–>組態管理員的活動解決方案平台,活動解決方案平台–>建立–>選擇 x64即可

接下來我們需要將我們的共用庫添加進來:

  • 首先我們調用System.out.println( System.getProperty(“java.library.path”));獲得共用庫的路徑
  • 接下來我們將dll檔案拷貝到其中之一的路徑中

5.載入共用庫,調用native方法:

package com.example.jnitest;public class Main {    static{        System.loadLibrary("JniApplication");//靜態初始化塊載入庫    }    public static void main(String[] args) {        JniTest.hello();    }}

在控制台中輸出結果:

Jni中資料類型對應關係

瞭解了jni的簡單用法,我們再來進行深入的學習:首先使jni中Java基本類型和C基本類型的對應關係

除了基本的資料類型外,比較常用的當屬字串類型,Java中的String對應C中為jstring,我們可以使用如下方法將jstring類型和char *的相互轉化

        char *ff = "Hello Jni!";        //建立一個jstring類型字串        jstring j_string = env->NewStringUTF(ff);        //將jstring類型轉化為char*        const char * c_string = env->GetStringUTFChars(j_string, 0);        printf(c_string);        //當我們不再使用字串時,要將其釋放        env->ReleaseStringUTFChars(j_string, c_string);
訪問Java中的域

使用C語言訪問Java中的域和方法都要知道其Id,使用起來相對也比較複雜,在這裡舉一個簡單的例子,假設我們的類People中有一個int類型的域age,即:

package com.example.jnitest;public class People {    private int age;    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}

修改JniTest中的方法,這一次我們通過調用native方法使得age域變為原來的二倍:

package com.example.jnitest;public class JniTest {    public static native People changeAge(People people);}

重建標頭檔後再cpp檔案中編寫我們的方法,每次訪問Java中的域我們需要得到FieldId才可以,其步驟為:
1. 擷取隱式參數的類
2. 擷取域Id
3. 訪問域的值

#include"jni_hello.h"JNIEXPORT jobject JNICALL Java_com_example_jnitest_JniTest_changeAge  (JNIEnv *env, jclass cl, jobject ob){      jclass cl_people = env->GetObjectClass(ob);      jfieldID c_ageId = env->GetFieldID(cl_people, "age", "I");      jint c_age = env->GetIntField(ob,c_ageId);      //列印修改之後的語句      printf("%d", c_age);      //修改age域      env->SetIntField(ob, c_ageId, 2*c_age);      return ob;}

env->GetFieldID方法中的第二個參數表示該域的名稱,第三個參數表示該age域的類型為int,這個jni的編碼簽名有關,稍後會提到。接下來在main方法中調用:

package com.example.jnitest;public class Main {    static{        System.loadLibrary("JniApplication");    }    public static void main(String[] args) {        People people = new People();        people.setAge(5);        people = JniTest.changeAge(people);        System.out.println("changed::"+people.getAge());    }}

輸出結果:

可以看到,結果變為原來的2倍。對於訪問靜態域時只需更換env->GetIntField為env->GetStaticIntField即可。

訪問Java中的方法

剛剛我們使用了訪問域的方法去修改了域的值,下面我們在cpp檔案中調用People類下的setAge方法修改age的值;調用Java的方法需要按照以下步驟:

#include"jni_hello.h"JNIEXPORT jobject JNICALL Java_com_example_jnitest_JniTest_changeAge  (JNIEnv *env, jclass cl, jobject ob){      jclass cl_people = env->GetObjectClass(ob);      jfieldID c_ageId = env->GetFieldID(cl_people, "age", "I");      jint c_age = env->GetIntField(ob,c_ageId);      //第三個參數同樣和編碼簽名有關      jmethodID c_setAgeId = env->GetMethodID(cl_people, "setAge","(I)V");      //void表示了傳回型別,若返回為int,則調用CallIntMethod      env->CallVoidMethod(ob,c_setAgeId,2*c_age);      return ob;}

在main函數中調用,代碼同上,結果如下:

對於static方法的調用,我們只需要將調用時的方法更換就好,例如setAge方法為static的話,只需將 env->CallVoidMethod(ob,c_setAgeId,2*c_age);更換為 env->CallStaticVoidMethod(ob,c_setAgeId,2*c_age);即可。

Jni編碼簽名

在前面我們提到了env->GetFieldID方法中的第三個參數用編碼簽名來表示了該域的資料類型,現在來介紹一下jni中資料類的對應關係。

  • 基礎資料型別 (Elementary Data Type):
    B——byte, C——char, D——double, F——float,
    I——-int , J——long, S——short, V——–void , Z——boolean
  • 數群組類型,在開頭加“[”來表示,如int[] 數組為“[I”;二維數組int[][] 即為“[[I”
  • 類類型,以“L+完整包名+類名+;”表示其中所有的“.”換為“/”,如com.example.jnitest.People類表示為Lcom/example/jnitest/People;

關於方法的編碼簽名規則是,“(傳入參數類型的編碼簽名)+返回參數類型的編碼簽名”,多個傳入參數之間直接相接就好;例如我們的setAge方法中傳入參數為int,返回為void,則表示為“(I)V”。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.