概述 java
當沒法用 Java 語言編寫整個應用程序時,JNI 容許您使用本機代碼。在下列典型狀況下,您可能決定使用本機代碼: linux
從 Java 程序調用 C 或 C ++ 代碼的過程由六個步驟組成。 咱們將在下面幾頁中深刻討論每一個步驟,但仍是先讓咱們迅速地瀏覽一下它們。 shell
咱們從編寫 Java 源代碼文件開始,它將聲明本機方法(或方法),裝入包含本機代碼的共享庫,而後實際調用本機方法。 編程
這裏是名爲 Sample1.java 的 Java 源代碼文件的示例: jvm
public class Sample1 { public native int intMethod(int n); public native boolean booleanMethod(boolean bool); public native String stringMethod(String text); public native int intArrayMethod(int[] intArray); public static void main(String[] args) { System.loadLibrary("Sample1"); Sample1 sample = new Sample1(); int square = sample.intMethod(5); boolean bool = sample.booleanMethod(true); String text = sample.stringMethod("JAVA"); int sum = sample.intArrayMethod( new int[]{1,1,2,3,5,8,13} ); System.out.println("intMethod: " + square); System.out.println("booleanMethod: " + bool); System.out.println("stringMethod: " + text); System.out.println("intArrayMethod: " + sum); } }
首先,請注意對 native
關鍵字的使用,它只能隨方法一塊兒使用。native
關鍵字告訴 Java 編譯器:方法是用 Java 類以外的本機代碼實現的,但其聲明卻在 Java 中。只能在 Java 類中聲明本機方法,而不能實現它,因此本機方法不能擁有方法主體。 編程語言
如今,讓咱們逐行研究一下代碼: 函數
native
方法。 注:基於 UNIX 的平臺上的共享庫文件一般含有前綴「lib」。在本例中,第 10 行多是 System.loadLibrary("libSample1");
。請必定要注意您在步驟 5:建立共享庫文件中生成的共享庫文件名。 工具
Jerikc:
Java不是完美的,Java的不足除了體如今運行速度上要比傳統的C++慢以外,Java沒法直接訪問到操做系統底層(如系統硬件等),爲此Java使用native方法來擴展Java程序的功能。
能夠將native方法比做Java程序同C/C++程序的接口。
接下來,咱們須要將 Java 代碼編譯成字節碼。完成這一步的方法之一是使用隨 SDK 一塊兒提供的 Java 編譯器 javac
。 用來將 Java 代碼編譯成字節碼的命令是: spa
javac Sample1.java
第三步是建立 C/C++ 頭文件,它定義本機函數說明。完成這一步的方法之一是使用 javah.exe,它是隨 SDK 一塊兒提供的本機方法 C 存根生成器工具。這個工具被設計成用來建立頭文件,該頭文件爲在 Java 源代碼文件中所找到的每一個 native
方法定義 C 風格的函數。這裏使用的命令是: 操作系統
javah Sample1
Sample1.java
上運行 javah.exe
的結果下面的 Sample1.h 是對咱們的 Java 代碼運行 javah 工具所生成的 C/C++ 頭文件:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Sample1 */ #ifndef _Included_Sample1 #define _Included_Sample1 #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint); JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod (JNIEnv *, jobject, jboolean); JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *, jobject, jstring); JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod (JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
正如您可能已經注意到的那樣,Sample1.h 中的 C/C++ 函數說明和 Sample1.java 中的 Java native
方法聲明有很大差別。JNIEXPORT
和 JNICALL
是用於導出函數的、依賴於編譯器的指示符。返回類型是映射到 Java 類型的 C/C++ 類型。附錄 A:JNI 類型中完整地說明了這些類型。
除了 Java 聲明中的通常參數之外,全部這些函數的參數表中都有一個指向 JNIEnv
和 jobject
的指針。指向 JNIEnv
的指針其實是一個指向函數指針表的指針。正如將要在步驟 4 中看到的,這些函數提供各類用來在 C 和 C++ 中操做 Java 數據的能力。
jobject
參數引用當前對象。所以,若是 C 或 C++ 代碼須要引用 Java 函數,則這個 jobject
充當引用或指針,返回調用的 Java 對象。函數名自己是由前綴「Java_
」加全限定類名,再加下劃線和方法名構成的。
當談到編寫 C/C++ 函數實現時,有一點須要牢記:說明必須和 Sample1.h 的函數聲明徹底同樣。咱們將研究用於 C 實現和 C++ 實現的完整代碼,而後討論二者之間的差別。
如下是 Sample1.c,它是用 C 編寫的實現:
#include "Sample1.h" #include <string.h> JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *env, jobject obj, jint num) { return num * num; } JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod (JNIEnv *env, jobject obj, jboolean boolean) { return !boolean; } JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *env, jobject obj, jstring string) { const char *str = (*env)->GetStringUTFChars(env, string, 0); char cap[128]; strcpy(cap, str); (*env)->ReleaseStringUTFChars(env, string, str); return (*env)->NewStringUTF(env, strupr(cap)); } JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod (JNIEnv *env, jobject obj, jintArray array) { int i, sum = 0; jsize len = (*env)->GetArrayLength(env, array); jint *body = (*env)->GetIntArrayElements(env, array, 0); for (i=0; i<len; i++) { sum += body[i]; } (*env)->ReleaseIntArrayElements(env, array, body, 0); return sum; } void main(){}
如下是 Sample1.cpp(C++ 實現)
1. #include "Sample1.h" 2. #include <string.h> 3. 4.JNIEXPORT jint JNICALL Java_Sample1_intMethod 5. (JNIEnv *env, jobject obj, jint num) { 6. return num * num; 7. } 8. 9. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod 10. (JNIEnv *env, jobject obj, jboolean boolean) { 11. return !boolean; 12. } 13. 14. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod 15. (JNIEnv *env, jobject obj, jstring string) { 16. const char *str = env->GetStringUTFChars(string, 0); 17. char cap[128]; 18. strcpy(cap, str); 19. env->ReleaseStringUTFChars(string, str); 20. return env->NewStringUTF(strupr(cap)); 21. } 22. 23. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod 24. (JNIEnv *env, jobject obj, jintArray array) { 25. int i, sum = 0; 26. jsize len = env->GetArrayLength(array); 27. jint *body = env->GetIntArrayElements(array, 0); 28. for (i=0; i<len; i++) 29. { sum += body[i]; 30. } 31. env->ReleaseIntArrayElements(array, body, 0); 32. return sum; 33. } 34. 35. void main(){}
C 和 C++ 代碼幾乎相同;惟一的差別在於用來訪問 JNI 函數的方法。在 C 中,JNI 函數調用由「(*env)->
」做前綴,目的是爲了取出函數指針所引用的值。在 C++ 中,JNIEnv
類擁有處理函數指針查找的內聯成員函數。下面將說明這個細微的差別,其中,這兩行代碼訪問同一函數,但每種語言都有各自的語法。
C 語法: jsize len = (*env)->GetArrayLength(env,array); C++ 語法: jsize len =env->GetArrayLength(array);
接下來,咱們建立包含本機代碼的共享庫文件。大多數 C 和 C++ 編譯器除了能夠建立機器代碼可執行文件之外,也能夠建立共享庫文件。用來建立共享庫文件的命令取決於您使用的編譯器。下面是在 Windows 和 Solaris 系統上執行的命令。
Windows: cl -Ic:\jdk\include -Ic:\jdk\include\win32 -LD Sample1.c -FeSample1.dll Solaris: cc -G -I/usr/local/jdk/include -I/user/local/jdk/include/solaris Sample1.c -o Sample1.so
Jerikc:
Ubuntu Linux :
gcc -shared -I /usr/lib/jvm/java-6-sun-1.6.0.20/include -I /usr/lib/jvm/java-6-sun-1.6.0.20/include/linux -I /usr/include Sample1.c -o Sample1.so
解釋一下上面的操做,固然個人系統是Ubuntu10.04,不過關於jdk安裝以後的路徑應該大同小異,本身作下適當的調整便可
/usr/lib/jvm/java-6-sun-1.6.0.20/include下包含的jni.h
/usr/lib/jvm/java-6-sun-1.6.0.20/include/linux下包含的是jni-mid.h
-shared 是設置生成share object的標誌
libtest.so 即爲咱們須要的動態連接庫(等同於Windows下的*.dll文件)
最後一步是運行 Java 程序,並確保代碼正確工做。由於必須在 Java 虛擬機中執行全部 Java 代碼,因此須要使用 Java 運行時環境。完成這一步的方法之一是使用 java
,它是隨 SDK 一塊兒提供的 Java 解釋器。所使用的命令是:
java Sample1
當運行 Sample1.class
程序時,應該得到下列結果:
PROMPT>java Sample1 intMethod: 25 booleanMethod: false stringMethod: JAVA intArrayMethod: 33 PROMPT>
當使用 JNI 從 Java 程序訪問本機代碼時,您會遇到許多問題。您會遇到的三個最多見的錯誤是:
java.lang.UnsatisfiedLinkError
。這一般指沒法找到共享庫,或者沒法找到共享庫內特定的本機方法。System.loadLibrary(String libname)
方法(參數是文件名)裝入庫文件時,請確保文件名拼寫正確以及沒有指定擴展名。還有,確保庫文件的位置在類路徑中,從而確保 JVM 能夠訪問該庫文件。
從 Java 調用 C 或 C++ 本機代碼(雖然不簡單)是 Java 平臺中一種良好集成的功能。雖然 JNI 支持 C 和 C++,但 C++ 接口更清晰一些而且一般比 C 接口更可取。
正如您已經看到的,調用 C 或 C++ 本機代碼須要賦予函數特殊的名稱,並建立共享庫文件。當利用現有代碼庫時,更改代碼一般是不可取的。要避免這一點,在 C++ 中,一般建立代理代碼或代理類,它們有專門的 JNI 所需的命名函數。而後,這些函數能夠調用底層庫函數,這些庫函數的說明和實現保持不變。