天天看點

java 如何使用dylib_Java JNI的使用基礎

JNI是Java與C、C++、Objective-C、Objective-C++等靜态編譯語言以及彙編語言互相動的接口。盡管目前而言,Java提供了諸多運作時性能較高的運作時庫,但是在很多方面,尤其是高性能計算領域,Java提供的高效庫還不是很多,是以我們可以通過JNI接口将我們用靜态語言以及彙編編譯連接配接為動态庫後給Java應用程式加載調用。

首先,Java為不同的作業系統平台提供了各自相适應的運作時環境以及根據不同的編譯器提供了JNI頭檔案。JNI頭檔案一般由兩個組成:jni_md.h提供了依賴于平台的頭檔案;jni.h提供了jni所需要的接口聲明以及各種類型的定義。這兩個頭檔案都可以在JDK的include中找到。我們在建立一個JNI動态庫的工程時應該将工程的輸出目标設定為動态連接配接庫(瘟抖死下為.dll,Unix-like系統下為.so,OS X下為.dylib)。我們在建立工程時可以将這兩個頭檔案導入到工程中。

然後我們可以看以下代碼:

#include

#include "jni.h"

JNIEXPORT jint JNICALL Java_MyJNI_myTest(JNIEnv *env, jobject obj, jintArray dstArray, jintArray srcArray)

{

jboolean isCopy = 0;

jint* csrcArray = (*(*env)->GetIntArrayElements)(env, srcArray, &isCopy);

jsize dstSize = (*(*env)->GetArrayLength)(env, dstArray);

jsize srcSize = (*(*env)->GetArrayLength)(env, srcArray);

jsize length = dstSize >= srcSize? dstSize : srcSize;

printf("The length is: %u\n", length);

printf("Is copy available? %d\n", isCopy);

printf("The sum of source array is: %d", csrcArray[0] + csrcArray[1]);

jint dstBuffer[32];

for(jsize i = 0; i < length; i++)

dstBuffer[i] = csrcArray[i] + i + 100;

(*(*env)->SetIntArrayRegion)(env, dstArray, 0, length, dstBuffer);

return 100;

}

以上代碼提供了一個導出Java方法,Java_後面的名字表示在Java應用端的類名,類名後面的_所跟的名字是該類的方法名(該方法可以是成員方法也可以是類方法)。一個标準的JNI方法應該提供兩個形參,一個是JNIEnv*,另一個是jobject。env提供了Java運作時環境的句柄,後面調用各種Java運作時方法都需要傳這個參數。jobject是指向該對象的指針,相當于Java應用端中的this。是以如果你所定義的這個JNI方法在Java應用端是一個類方法,那麼這個參數即被忽略。

以上方法實作的功能是将第二個數組的每個元素加上100再加其索引後的值相應指派給第一個目的數組元素。這邊假定兩個數組的元素個數最多為32。最後傳回一個int類型的值100。

然後,我們可以看Java端相應的代碼:

class MyJNI {

native static int myTest(int[] dstArray, int[] srcArray);

}

public class Test {

static {

System.loadLibrary("MyJNI");

}

public static void main(String[] args) {

// TODO Auto-generated method stub

int[] dstArray = new int[2];

int[] srcArray = {100, 600};

System.out.println("The value is: " + MyJNI.myTest(dstArray, srcArray));

System.out.println("The sum is: " + (dstArray[0] + dstArray[1]));

}

}

這裡調用System.loadLibrary方法來加載動态連接配接庫。

在使用Linux環境時必須注意,在運作前必須設定LD_LIBRARY_PATH環境變量以指定動态庫所在的路徑,比如:

LD_LIBRARY_PATH='/usr/home/java_test'

export LD_LIBRARY_PATH

如果使用Eclipse開發環境的話,我們可以在目前項目的Run Configuration以及Debug Configuration中來設定此環境變量。友善起見,我們在設定路徑時可以使用右側的variable按鈕,選擇project_loc。這個内建變量指定了目前項目的系統絕對路徑。然後把動态庫導入到目前項目的根目錄下即可。

最後,我們舉一個更為完整的例子。這個例子中,我們在JNI代碼中将加入對彙編語言的一起編譯連結,做成.so檔案。這裡要注意的是,由于彙編器不支援-fPIC選項,是以要與彙編檔案一起連結的話就不能帶-fPIC的編譯選項了,否則連接配接就會失敗。

我們先看Java代碼:

packagetest;public classMain {static{

System.loadLibrary(("ctest"));

}native static intmyJNITest();public static voidmain(String[] args) {//TODO Auto-generated method stub

System.out.println("The answer is: " +myJNITest());

}

}

這裡加入了一個package——test,是以,對于JNI的native函數myJNITest而言,其字首就需要把包名加上,變為——

Java_test_Main_myJNITest。

我們下面就看JNI部分。我們這裡可以再建立一個JNI的工程,友善後面使用shell進行編譯。首先,在此工程裡建立一個C源檔案,檔案名可以随便命名,.c作為字尾即可,這裡用a.c:

#include

extern int __attribute__((fastcall)) asmTest(void);

JNIEXPORT jint JNICALL Java_test_Main_myJNITest(JNIEnv*env, jobject obj)

{return 100 +asmTest();

}

這裡的asmTest函數就定義在一個彙編檔案裡,下面将會看到。這裡使用fastcall調用規則,使得參數以及傳回值都能在寄存器中,這樣可以友善彙編函數對實參的擷取。當然,在64位應用環境下就不需要fastcall了。因為64位環境下,x86-64遵循的是System-V的函數調用約定,具體可參考——http://www.cocoachina.com/bbs/read.php?tid-66986.html

下面看看彙編源檔案(asmtest.s):

.text

.align2.globl asmTestasmTest:

mov $15, %eaxret

完成了C源代碼以及彙編代碼之後,我們将寫一個簡單的shell檔案把它們分别編譯,然後再連接配接成一個so動态共享庫檔案。

gcc -Wall -c -I/home/zenny_chen/MyPrograms/eclipse/jdk/include -I/home/zenny_chen/MyPrograms/eclipse/jdk/include/linux a.cgcc -Wall -c asmtest.sgcc -shared -z noexecstack -o libctest.so a.o asmtest.o

由于jni.h在 jdk/include 下,而jni_md.h則是在 jdk/include/linux 下(其它作業系統則是其它作業系統的名稱),是以,這裡要把兩個頭檔案包含路徑都加上。另外,最後的-z noexecstack要加,因為Java會對棧進行檢查,如果沒有此連接配接選項,Java在調用此函數時就會報warning(比較煩人,呼呼~)。最後,連接配接生成的庫名就是libctest.so,這個檔案名必須與Java中loadLibrary的庫名一緻。