轉載請注明出處:http://blog.csdn.net/xyang81/article/details/41777471
JNI全稱是Java Native Interface(Java本地接口)單詞首字母的縮寫,本地接口就是指用C和C++開發的接口。由於JNI是JVM規范中的一部份,因此可以將我們寫的JNI程序在任何實現了JNI規范的Java虛擬機中運行。同時,這個特性使我們可以復用以前用C/C++寫的大量代碼。
開發JNI程序會受到系統環境的限制,因為用C/C++語言寫出來的代碼或模塊,編譯過程當中要依賴當前操作系統環境所提供的一些庫函數,並和本地庫鏈接在一起。而且編譯后生成的二進制代碼只能在本地操作系統環境下運行,因為不同的操作系統環境,有自己的本地庫和CPU指令集,而且各個平台對標准C/C++的規范和標准庫函數實現方式也有所區別。這就造成使用了JNI接口的JAVA程序,不再像以前那樣自由的跨平台。如果要實現跨平台,就必須將本地代碼在不同的操作系統平台下編譯出相應的動態庫。
JNI開發流程主要分為以下6步:
1、編寫聲明了native方法的Java類
2、將Java源代碼編譯成class字節碼文件
3、用javah -jni命令生成.h頭文件(javah是jdk自帶的一個命令,-jni參數表示將class中用native聲明的函數生成jni規則的函數)
4、用本地代碼實現.h頭文件中的函數
5、將本地代碼編譯成動態庫(windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
6、拷貝動態庫至 java.library.path 本地庫搜索目錄下,並運行Java程序
通過上面的介紹,相信大家對JNI及開發流程有了一個整體的認識,下面通過一個HelloWorld的示例,再深入了解JNI開發的各個環節及注意事項。
PS:本人的開發環境為Mac os x 10.10.1 ,Eclipse 3.8(Juno),如果在其它操作系統下開發也是一樣,只需將本地代碼編譯成當前操作系統所支持的動態庫即可。
這個案例用命令行的方式介紹開發流程,這樣大家對JNI開發流程的印象會更加深刻,后面的案例都采用eclipse+cdt來開發。
第一步、並新建一個HelloWorld.java源文件
package com.study.jnilearn; public class HelloWorld { public static native String sayHello(String name); // 1.聲明這是一個native函數,由本地代碼實現 public static void main(String[] args) { String text = sayHello("yangxin"); // 3.調用本地函數 System.out.println(text); } static { System.loadLibrary("HelloWorld"); // 2.加載實現了native函數的動態庫,只需要寫動態庫的名字 } }第二步、用javac命令將.java源文件編譯成.class字節碼文件
注意:HelloWorld放在com.study.jnilearn包下面
javac src/com/study/jnilearn/HelloWorld.java -d ./bin-d 表示將編譯后的class文件放到指定的目錄下,這里我把它放到和src同級的bin目錄下
第三步、用javah -jni命令,根據class字節碼文件生成.h頭文件(-jni參數是可選的)
javah -jni -classpath ./bin -d ./jni com.study.jnilearn.HelloWorld默認生成的.h頭文件名為:com_study_jnilearn_HelloWorld.h(包名+類名.h),也可以通過-o參數指定生成頭文件名稱:
javah -jni -classpath ./bin -o HelloWorld.h com.study.jnilearn.HelloWorld參數說明:
-classpath :類搜索路徑,這里表示從當前的bin目錄下查找
-d :將生成的頭文件放到當前的jni目錄下
-o : 指定生成的頭文件名稱,默認以類全路徑名生成(包名+類名.h)
注意:-d和-o只能使用其中一個參數。
第四步、用本地代碼實現.h頭文件中的函數
com_study_jnilearn_HelloWorld.h:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_study_jnilearn_HelloWorld */ #ifndef _Included_com_study_jnilearn_HelloWorld #define _Included_com_study_jnilearn_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: com_study_jnilearn_HelloWorld * Method: sayHello * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello (JNIEnv *, jclass, jstring); #ifdef __cplusplus } #endif #endif
HelloWorld.c:
// HelloWorld.c #include "com_study_jnilearn_HelloWorld.h" #ifdef __cplusplus extern "C" { #endif /* * Class: com_study_jnilearn_HelloWorld * Method: sayHello * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello( JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = NULL; char buff[128] = { 0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL); if (c_str == NULL) { printf("out of memory.\n"); return NULL; } (*env)->ReleaseStringUTFChars(env, j_str, c_str); printf("Java Str:%s\n", c_str); sprintf(buff, "hello %s", c_str); return (*env)->NewStringUTF(env, buff); } #ifdef __cplusplus } #endif第五步、將C/C++代碼編譯成本地動態庫文件
動態庫文件名命名規則:lib+動態庫文件名+后綴(操作系統不一樣,后綴名也不一樣)如:
Mac OS X : libHelloWorld.jnilib
Windows :HelloWorld.dll(不需要lib前綴)
Linux/Unix:libHelloWorld.so
1> Mac OS X
gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin我的$JAVA_HOME目錄在:/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home
參數選項說明:
-dynamiclib:表示編譯成動態鏈接庫
-o:指定動態鏈接庫編譯后生成的路徑及文件名
-framework JavaVM -I:編譯JNI需要用到JVM的頭文件(jni.h),第一個目錄是平台無關的,第二個目錄是與操作系統平台相關的頭文件
2> Windows(以Windows7下VS2012為例)
開始菜單-->所有程序-->Microsoft Visual Studio 2012-->打開VS2012 X64本機工具命令提示,用cl命令編譯成dll動態庫:
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloWorld.c -FeHelloWorld.dll
參數選項說明:
-I : 和mac os x一樣,包含編譯JNI必要的頭文件
-LD:標識將指定的文件編譯成動態鏈接庫
-Fe:指定編譯后生成的動態鏈接庫的路徑及文件名
3> Linux/Unix
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloWorld.c -o libHelloWorld.so參數說明:
-I: 包含編譯JNI必要的頭文件
-fPIC: 編譯成與位置無關的獨立代碼
-shared:編譯成動態庫
-o: 指定編譯后動態庫生成的路徑和文件名
第六步、運行Java程序
Java在調用native(本地)方法之前,需要先加載動態庫。如果在未加載動態之前就調用native方法,會拋出找不到動態鏈接庫文件的異常。如下所示:
Exception in thread "main" java.lang.UnsatisfiedLinkError: com.study.jnilearn.HelloWorld.sayHello(Ljava/lang/String;)Ljava/lang/String; at com.study.jnilearn.HelloWorld.sayHello(Native Method) at com.study.jnilearn.HelloWorld.main(HelloWorld.java:9)
一般在類的靜態(static)代碼塊中加載動態庫最合適,因為在創建類的實例時,類會被ClassLoader先加載到虛擬機,隨后立馬調用類的static靜態代碼塊。這時再去調用native方法就萬無一失了。加載動態庫的兩種方式:
System.loadLibrary("HelloWorld"); System.load("/Users/yangxin/Desktop/libHelloWorld.jnilib");方式1:只需要指定動態庫的名字即可,不需要加lib前綴,也不要加.so、.dll和.jnilib后綴
方式2:指定動態庫的絕對路徑名,需要加上前綴和后綴
如果使用方式1,java會去java.library.path系統屬性指定的目錄下查找動態庫文件,如果沒有找到會拋出java.lang.UnsatisfiedLinkError異常。
Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld2 in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860) at java.lang.Runtime.loadLibrary0(Runtime.java:845) at java.lang.System.loadLibrary(System.java:1084) at com.study.jnilearn.HelloWorld.<clinit>(HelloWorld.java:13)大家從異常中可以看出來,他是在java.library.path中查找該名稱對應的動態庫,如果在mac下找libHelloWorld.jnilib文件,linux下找libHelloWorld.so文件,windows下找libHelloWorld.dll文件,可以通過調用System.getProperties("java.library.path")方法獲取查找的目錄列表,下面是我本機mac os x 系統下的查找目錄:
String libraryDirs = System.getProperties("java.library.path"); System.out.println(libraryDirs); // 輸出結果如下: /Users/yangxin/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.有兩種方式可以讓java從java.library.path找到動態鏈接庫文件,聰明的你應該已經想到了。
方式1:將動態鏈接庫拷貝到java.library.path目錄下
方式2:給jvm添加“-Djava.library.path=動態鏈接庫搜索目錄”參數,指定系統屬性java.library.path的值
java -Djava.library.path=/Users/yangxin/Desktop
Linux/Unix環境下可以通過設置LD_LIBRARY_PATH環境變量,指定庫的搜索目錄。
費了那么大勁,終於可以運行寫好的Java程序了,結果如下:
yangxin-MacBook-Pro:JNILearn yangxin$ java -classpath ./bin com.study.jnilearn.HelloWorld Java Str:yangxin hello yangxin
如果沒有將動態庫拷貝到本地庫搜索目錄下,執行java命令,可通過添加系統屬性java.library.path來指定動態庫的目錄,如下所示:
yangxin-MacBook-Pro:JNILearn yangxin$ java -Djava.library.path=/Users/yangxin/Desktop -classpath ./bin com.study.jnilearn.HelloWorld Java Str:yangxin hello yangxin