本文為在 32 位 windows 平台上實作 java 本地方法提供了實用的示例、步驟和準則。這些示例包括傳遞和傳回常用的資料類型。
本文中的示例使用 sun microsystems 公司建立的 java development kit (jdk) 版本 1.1.6 和 java 本地接口 (jni) 規範。用 c 語言編寫的本地代碼是用 microsoft visual c++ 編譯器編譯生成的。
本文提供調用本地 c 代碼的 java 代碼示例,包括傳遞和傳回某些常用的資料類型。本地方法包含在特定于平台的可執行檔案中。就本文中的示例而言,本地方法包含在 windows 32 位動态連結庫 (dll) 中。
不過我要提醒您,對 java 外部的調用通常不能移植到其他平台上,在 applet 中還可能引發安全異常。實作本地代碼将使您的 java 應用程式無法通過 100% 純 java 測試。但是,如果必須執行本地調用,則要考慮幾個準則:
将您的所有本地方法都封裝在單個類中,這個類調用單個 dll。對于每種目标作業系統,都可以用特定于适當平台的版本替換這個 dll。這樣就可以将本地代碼的影響減至最小,并有助于将以後所需的移植問題包含在内。
本地方法要簡單。盡量将您的 dll 對任何第三方(包括 microsoft)運作時 dll 的依賴減到最小。使您的本地方法盡量獨立,以将加載您的 dll 和應用程式所需的開銷減到最小。如果需要運作時 dll,必須随應用程式一起提供它們。
對于調用 c 函數的 java 方法,必須在 java 類中聲明一個本地方法。在本部分的所有示例中,我們将建立一個名為 mynative 的類,并逐漸在其中加入新的功能。這強調了一種思想,即将本地方法集中在單個類中,以便将以後所需的移植工作減到最少。
示例 1 -- 傳遞參數
在第一個示例中,我們将三個常用參數類型傳遞給本地函數:string、int 和 boolean。本例說明在本地 c 代碼中如何引用這些參數。
請注意,本地方法被聲明為專用的,并建立了一個包裝方法用于公用目的。這進一步将本地方法同代碼的其餘部分隔離開來,進而允許針對所需的平台對它進行優化。static 子句加載包含本地方法實作的 dll。
下一步是生成 c 代碼來實作 showparms0 方法。此方法的 c 函數原型是通過對 .class 檔案使用 javah 實用程式來建立的,而 .class 檔案是通過編譯 mynative.java 檔案生成的。這個實用程式可在 jdk 中找到。下面是 javah 的用法:
這将生成一個 mynative.h 檔案,其中包含一個本地方法原型,如下所示:
第一個參數是調用 jni 方法時使用的 jni environment 指針。第二個參數是指向在此 java 代碼中執行個體化的 java 對象 mynative 的一個句柄。其他參數是方法本身的參數。請注意,mynative.h 包括頭檔案 jni.h。jni.h 包含 jni api 和變量類型(包括jobject、jstring、jint、jboolean,等等)的原型和其他聲明。
本地方法是在檔案 mynative.c 中用 c 語言實作的:
jni api,getstringutfchars,用來根據 java 字元串或 jstring 參數建立 c 字元串。這是必需的,因為在本地代碼中不能直接讀取 java 字元串,而必須将其轉換為 c 字元串或 unicode。有關轉換 java 字元串的詳細資訊,請參閱标題為 nls strings and jni 的一篇論文。但是,jboolean 和 jint 值可以直接使用。
mynative.dll 是通過編譯 c 源檔案建立的。下面的編譯語句使用 microsoft visual c++ 編譯器:
其中 c:/jdk1.1.6 是 jdk 的安裝路徑。
mynative.dll 已建立好,現在就可将其用于 mynative 類了。可以這樣測試這個本地方法:在 mynative 類中建立一個 main 方法來調用 showparms 方法,如下所示:
當運作這個 java 應用程式時,請確定 mynative.dll 位于 windows 的 path 環境變量所指定的路徑中或目前目錄下。當執行此 java 程式時,如果未找到這個 dll,您可能會看到以下的消息:
這是因為 static 子句無法加載這個 dll,是以在初始化 mynative 類時引發異常。java 解釋器處理這個異常,并報告一個一般錯誤,指出找不到這個類。如果用 -verbose 指令行選項運作解釋器,您将看到它因找不到這個 dll 而加載 unsatisfiedlinkerror 異常。
如果此 java 程式完成運作,就會輸出以下内容:
示例 2 -- 傳回一個值
本例将說明如何在本地方法中實作傳回代碼。将這個方法添加到 mynative 類中,這個類現在變為以下形式:
公用的 hypotenuse 方法調用本地方法 hypotenuse0 來根據傳遞的參數計算值,并将結果作為一個整數傳回。這個新本地方法的原型是使用 javah 生成的。請注意,每次運作這個實用程式時,它将自動覆寫目前目錄中的 mynative.h。按以下方式執行 javah:
生成的 mynative.h 現在包含 hypotenuse0 原型,如下所示:
該方法是在 mynative.c 源檔案中實作的,如下所示:
再次請注意,jint 和 int 值是可互換的。使用相同的編譯語句重新編譯這個 dll:
現在執行 java mynative 将輸出 5 和 15 作為斜邊的值。
示例 3 -- 靜态方法
您可能在上面的示例中已經注意到,執行個體化的 mynative 對象是沒必要的。實用方法通常不需要實際的對象,通常都将它們建立為靜态方法。本例說明如何用一個靜态方法實作上面的示例。更改 mynative.java 中的方法簽名,以使它們成為靜态方法:
現在運作 javah 為 hypotenuse0 建立一個新原型,生成的原型如下所示:
c 源代碼中的方法簽名變了,但代碼還保持原樣:
本質上,jobject 參數已變為 jclass 參數。此參數是指向 mynative.class 的一個句柄。main 方法可更改為以下形式:
因為方法是靜态的,是以調用它不需要執行個體化 mynative 對象。本文後面的示例将使用靜态方法。
示例 4 -- 傳遞數組
本例說明如何傳遞數組型參數。本例使用一個基本類型,boolean,并将更改數組元素。下一個示例将通路 string(非基本類型)數組。将下面的方法添加到 mynative.java 源代碼中:
在本例中,布爾型數組被初始化為 true,本地方法将把特定的元素設定為 false。同時,在 java 源代碼中,我們可以更改 main 以使其包含測試代碼:
在編譯源代碼并執行 javah 以後,mynative.h 頭檔案包含以下的原型:
請注意,布爾型數組是作為單個名為 jbooleanarray 的類型建立的。基本類型有它們自已的數組類型,如 jintarray 和 jchararray。非基本類型的數組使用 jobjectarray 類型。下一個示例中包括一個 jobjectarray。這個布爾數組的數組元素是通過 jni 方法 getbooleanarrayelements 來通路的。針對每種基本類型都有等價的方法。這個本地方法是如下實作的:
指向布爾型數組的指針可以使用 getbooleanarrayelements 獲得。數組大小可以用 getarraylength 方法獲得。使用 releasebooleanarrayelements 方法釋放數組。現在就可以讀取和修改數組元素的值了。jsize 聲明等價于 jint(要檢視它的定義,請參閱 jdk 的 include 目錄下的 jni.h 頭檔案)。
示例 5 -- 傳遞 java string 數組
本例将通過最常用的非基本類型,java string,說明如何通路非基本對象的數組。字元串數組被傳遞給本地方法,而本地方法隻是将它們顯示到控制台上。 mynative 類定義中添加了以下幾個方法:
并在 main 方法中添加了兩行進行測試:
本地方法分别通路每個元素,其實作如下所示。
數組元素可以通過 getobjectarrayelement 通路。在本例中,我們知道傳回值是 jstring 類型,是以可以安全地将它從 jobject 類型轉換為 jstring 類型。字元串是通過前面讨論過的方法列印的。有關在 windows 中處理 java 字元串的資訊,請參閱标題為 nls strings and jni 的一篇論文。
示例 6 -- 傳回 java string 數組
最後一個示例說明如何在本地代碼中建立一個字元串數組并将它傳回給 java 調用者。mynative.java 中添加了以下幾個方法:
更改 main 以使 showstrings 将 getstrings 的輸出顯示出來:
實作的本地方法傳回五個字元串。
字元串數組是通過調用 newobjectarray 建立的,同時傳遞了 string 類和數組長度兩個參數。java string 是使用 newstringutf 建立的。string 元素是使用 setobjectarrayelement 存入數組中的。
現在您已經為您的應用程式建立了一個本地 dll,但在調試時還要牢記以下幾點。如果使用 java 調試器 java_g.exe,則還需要建立 dll 的一個“調試”版本。這隻是表示必須建立同名但帶有一個 _g 字尾的 dll 版本。就 mynative.dll 而言,使用 java_g.exe 要求在 windows 的 path 環境指定的路徑中有一個 mynative_g.dll 檔案。在大多數情況下,這個 dll 可以通過将原檔案重命名或複制為其名稱帶綴 _g 的檔案。
現在,java 調試器不允許您進入本地代碼,但您可以在 java 環境外使用 c 調試器(如 microsoft visual c++)調試本地方法。首先将源檔案導入一個項目中。将編譯設定調整為在編譯時将 include 目錄包括在内:
将配置設定為以調試模式編譯 dll。在 project settings 中的 debug 下,将可執行檔案設定為 java.exe(或者 java_g.exe,但要確定您生成了一個 _g.dll 檔案)。程式參數包括包含 main 的類名。如果在 dll 中設定了斷點,則當調用本地方法時,執行将在适當的地方停止。
下面是設定一個 visual c++ 6.0 項目來調試本地方法的步驟。
在 visual c++ 中建立一個 win32 dll 項目,并将 .c 和 .h 檔案添加到這個項目中。
在 tools 下拉式菜單的 options 設定下設定 jdk 的 include 目錄。下面的對話框顯示了這些目錄。
選擇 build 下拉式菜單下的 build mynative.dll 來建立這個項目。確定将項目的活動配置設定為調試(這通常是預設值)。
在 project settings 下,設定 debug 頁籤來調用适當的 java 解釋器,如下所示:
當執行這個程式時,忽略“在 java.exe 中找不到任何調試資訊”的消息。當調用本地方法時,在 c 代碼中設定的任何斷點将在适當的地方停止 java 程式的執行。
jni 方法和 c++
上面這些示例說明了如何在 c 源檔案中使用 jni 方法。如果使用 c++,則請将相應方法的格式從:
更改為:
在 c++ 中,jni 函數被看作是 jnienv 類的成員方法。
字元串和國家語言支援
本文中使用的技術用 utf 方法來轉換字元串。使用這些方法隻是為了友善起見,如果應用程式需要國家語言支援 (nls),則不能使用這些方法。有關在 windows 和 nls 環境中處理 java 字元串正确方法,請參标題為 nls strings and jni 的一篇論文。
本文提供的示例用最常用的資料類據(如 jint 和 jstring)說明了如何實作本地方法,并讨論了 windows 特定的幾個問題,如顯示字元串。本文提供的示例并未包括全部 jni,jni 還包括其他參數類型,如 jfloat、jdouble、jshort、jbyte 和 jfieldid,以及用來處理這些類型的方法。有關這個主題的詳細資訊,請參閱 sun microsystems 提供的 java 本地接口規範。