JNI/NDK開發指南(1):JNI開發流程及HelloWorld

發表於2015-03-19

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原始檔

第二步、用javac命令將.java原始檔編譯成.class位元組碼檔案

注意:HelloWorld放在com.study.jnilearn包下面

-d 表示將編譯後的class檔案放到指定的目錄下,這裡我把它放到和src同級的bin目錄下

第三步、用javah -jni命令,根據class位元組碼檔案生成.h標頭檔案-jni引數是可選的

預設生成的.h標頭檔案名為:com_study_jnilearn_HelloWorld.h(包名+類名.h),也可以通過-o引數指定生成標頭檔案名稱:

引數說明:

-classpath :類搜尋路徑,這裡表示從當前的bin目錄下查詢

-d :將生成的標頭檔案放到當前的jni目錄下

-o : 指定生成的標頭檔案名稱,預設以類全路徑名生成(包名+類名.h)

注意:-d和-o只能使用其中一個引數。

第四步、用原生程式碼實現.h標頭檔案中的函式

com_study_jnilearn_HelloWorld.h:

HelloWorld.c:

第五步、將C/C++程式碼編譯成本地動態庫檔案

動態庫檔名命名規則:lib+動態庫檔名+字尾(作業系統不一樣,字尾名也不一樣)如:

Mac OS X : libHelloWorld.jnilib

Windows :HelloWorld.dll(不需要lib字首)

Linux/Unix:libHelloWorld.so

1> Mac OS X

我的$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動態庫:

引數選項說明:

-I :   和mac os x一樣,包含編譯JNI必要的標頭檔案

-LD:標識將指定的檔案編譯成動態連結庫

-Fe:指定編譯後生成的動態連結庫的路徑及檔名

3> Linux/Unix

引數說明:

-I:          包含編譯JNI必要的標頭檔案

-fPIC:    編譯成與位置無關的獨立程式碼

-shared:編譯成動態庫

-o:         指定編譯後動態庫生成的路徑和檔名

第六步、執行Java程式

Java在呼叫native(本地)方法之前,需要先載入動態庫。如果在未載入動態之前就呼叫native方法,會丟擲找不到動態連結庫檔案的異常。如下所示:

一般在類的靜態(static)程式碼塊中載入動態庫最合適,因為在建立類的例項時,類會被ClassLoader先載入到虛擬機器,隨後立馬呼叫類的static靜態程式碼塊。這時再去呼叫native方法就萬無一失了。載入動態庫的兩種方式:

方式1:只需要指定動態庫的名字即可,不需要加lib字首,也不要加.so、.dll和.jnilib字尾

方式2:指定動態庫的絕對路徑名,需要加上字首和字尾

如果使用方式1,java會去java.library.path系統屬性指定的目錄下查詢動態庫檔案,如果沒有找到會丟擲java.lang.UnsatisfiedLinkError異常。

大家從異常中可以看出來,他是在java.library.path中查詢該名稱對應的動態庫,如果在mac下找libHelloWorld.jnilib檔案,linux下找libHelloWorld.so檔案,windows下找libHelloWorld.dll檔案,可以通過呼叫System.getProperties(“java.library.path”)方法獲取查詢的目錄列表,下面是我本機mac os x 系統下的查詢目錄:

有兩種方式可以讓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程式了,結果如下:

如果沒有將動態庫拷貝到本地庫搜尋目錄下,執行java命令,可通過新增系統屬性java.library.path來指定動態庫的目錄,如下所示:

相關文章