java 呼叫c++動態連結庫
JNI其實是Java Native Interface的簡稱,也就是java本地介面。它提供了若干的API實現了和Java和其他語言的通訊(主要是C&C++)。也許不少人覺得Java已經足夠強大,為什麼要需要JNI這種東西呢?我們知道Java是一種平臺無關性的語言,平臺對於上層的java程式碼來說是透明的,所以在多數時間我們是不需要JNI的,但是假如你遇到了如下的三種情況之一呢? 1.你的Java程式碼,需要得到一個檔案的屬性。但是你找遍了JDK幫助文件也找不到相關的API。 2.在本地還有一個別的系統,不過他不是Java語言實現的,這個時候你的老闆要求你把兩套系統整合到一起。 3.你的Java程式碼中需要用到某種演算法,不過演算法是用C實現並封裝在動態連結庫檔案(DLL)當中的。 對於上述的三種情況,如果沒有JNI的話,那就會變得異常棘手了。就算找到解決方案了,也是費時費力。其實說到底還是會增加開發和維護的成本。 說了那麼多一通廢話,現在進入正題。看過JDK原始碼的人肯定會注意到在原始碼裡有很多標記成native的方法。這些個方法只有方法簽名但是沒有方法體。其實這些naive方法就是我們說的 java native interface。他提供了一個呼叫(invoke)的介面,然後用C或者C++去實現。我們首先來編寫這個“橋樑”.我自己的開發環境是j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0,先在eclipse裡建立一個HelloFore的Java工程,然後編寫下面的程式碼。
一般的第一個程式總是HelloWorld。今天換換口味,把world換成一個名字。我的native本地方法有一個String的引數。會傳遞一個name到後臺去。本地方法已經完成,現在來介紹下javah這個方法,接下來就要用javah方法來生成一個相對應的.h標頭檔案。 javah是一個專門為JNI生成標頭檔案的一個命令。CMD開啟控制檯之後輸入javah回車就能看到javah的一些引數。在這裡就不多介紹 我們要用的是 -jni這個引數,這個引數也是預設的引數,他會生成一個JNI式的.h標頭檔案。在控制檯進入到工程的根目錄,也就是HelloFore這個目錄,然後輸入命令。 Java程式碼 javah -jni com.chnic.jni.SayHellotoCPP命令執行完之後在工程的根目錄就會發現com_chnic_jni_SayHellotoCPP.h 這個標頭檔案。在這裡有必要多句嘴,在執行javah的時候,要輸入完整的包名+類名。否則在以後的測試呼叫過程中會發生java.lang.UnsatisfiedLinkError這個異常。 到這裡java部分算是基本完成了,接下來我們來編寫後端的C++程式碼。(用C也可以,只不過cout比printf用起來更快些,所以這裡俺偷下懶用C++)開啟VC++首先新建一個Win32 Dynamic-Link library工程,之後選擇An empty DLL project空工程。在這裡我C++的工程是HelloEnd,把剛剛生成的那個標頭檔案拷貝到這個工程的根目錄裡。隨便用什麼文字編輯器開啟這個標頭檔案,發現有一個如下的方法簽名。 Cpp程式碼
仔細觀察一下這個方法,在註釋上標註類名、方法名、簽名(Signature),至於這個簽名是做什麼用的,我們以後再說。在這裡最重要的是Java_com_chnic_jni_SayHellotoCPP_sayHello這個方法簽名。在Java端我們執行sayHello(String name)這個方法之後,JVM就會幫我們喚醒在DLL裡的Java_com_chnic_jni_SayHellotoCPP_sayHello這個方法。因此我們新建一個C++ source file來實現這個方法。 Cpp程式碼
因為我們生成的那個標頭檔案是在C++工程的根目錄不是在環境目錄,所以我們要把尖括號改成單引號,至於VC++的環境目錄可以在Tools->Options->Directories裡設定。F7編譯工程發現缺少jni.h這個標頭檔案。這個標頭檔案可以在%JAVA_HOME%\include目錄下找到。把這個檔案拷貝到C++工程目錄,繼續編譯發現還是找不到。原來是因為在我們剛剛生成的那個標頭檔案裡,jni.h這個檔案是被 #include <jni.h>引用進來的,因此我們把尖括號改成雙引號#include "jni.h",繼續編譯發現少了jni_md.h檔案,接著在%JAVA_HOME%\include\win32下面找到那個標頭檔案,放入到工程根目錄,F7編譯成功。在Debug目錄裡會發現生成了HelloEnd.dll這個檔案。 這個時候後端的C++程式碼也已經完成,接下來的任務就是怎麼把他們連線在一起了,要讓前端的java程式“認識並找到”這個動態連結庫,就必須把這個DLL放在windows path環境變數下面。有兩種方法可以做到: 1.把這個DLL放到windows下面的sysytem32資料夾下面,這個是windows預設的path (這樣好像不行,最後放到jdk/bin目錄下才 成功) 2.複製你工程的Debug目錄,我這裡是C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd\Debug這個目錄,把這個目錄配置到User variable的Path下面。重啟eclipse,讓eclipse在啟動的時候重新讀取這個path變數。 比較起來,第二種方法比較靈活,在開發的時候不用來回copy dll檔案了,節省了很多工作量,所以在開發的時候推薦用第二種方法。在這裡我們使用的也是第二種,eclipse重啟之後開啟SayHellotoCPP這個類。其實我們上面做的那些是不是是讓JVM能找到那些DLL檔案,接下來我們要讓我們自己的java程式碼“認識”這個動態連結庫。加入System.loadLibrary("HelloEnd");這句到靜態初始化塊裡。 Java程式碼
這樣我們的程式碼就能認識並載入這個動態連結庫檔案了。萬事俱備,只欠測試程式碼了,接下來編寫測試程式碼。 Java程式碼
我們不讓他直接Hello,World。我們把World傳進去,執行程式碼。發現控制檯打印出來Hello, World這句話。就此一個最簡單的JNI程式已經開發完成。也許有朋友會對CPP程式碼裡的 Cpp程式碼constchar* pname = env->GetStringUTFChars(name, NULL); 這句有疑問,這個GetStringUTFChars就是JNI給developer提供的API,我們以後再講。在這裡不得不多句嘴。
|