KVM本身只帶有cldc1.1的類庫,功能十分簡單,不能滿足用戶的需求,本篇介紹如何對KVM進行擴展。
對KVM進行擴展,在java層十分簡單,只要向在編譯Java代碼時多加一個文件就可以,沒什么要說的,麻煩的是如果在加入的Java類中有本地操作該怎么辦?本地的C語言代碼放在哪里編譯才能夠供KVM調用?
答案是KNI。下面就以KNI為主要內容介紹如何對KVM加以擴展,在最后附加一個具體的實現例子。
1. KNI的特點:
KNI(K Native Interface)是SUN的KVM(K Virtual Machine)所使用的本地方法調用機制。
JNI(Java Native Interface)是已經為我們所熟悉的Java本地方法調用機制,JNI一般使用在J2SE或J2EE平臺上,本地方法被編進動態鏈接庫,在運行時由Java虛擬機載入。
KVM中也需要本地調用,但JNI是“重量級”的本地調用方式,在使用時消耗的資源較多,所以針對KVM設計出了KNI,KNI被稱為是JNI的一個簡化版,是“輕量級”的本地調用方式。KVM不能加載動態鏈接庫,所以在KNI機制下,本地方法不是寫在庫中,而是編入虛擬機內部。
以下是KNI與JNI最重要的一些區別:
KNI是“實現層”的API,即它是虛擬機實現的一部分,修改KNI的API就要重新編譯虛擬機,這些API的細節對于Java程序員來說是不可見的;而JNI的API是在運行時動態加載進來的,它的修改與虛擬機無關,JNI的API對于Java程序員來說是可見的。
KNI的函數建在虛擬機內部,只能為此虛擬機所獨享;而JNI的函數放在動態鏈接庫中,可以為多個虛擬機共用。
由于在虛擬機內部,KNI的很多操作方式與虛擬機有關,在傳遞參數和控制對象的時候都要先經過一些特別的處理;JNI的調用方式比較直接,但可能會增加安全隱患。
KNI是JNI的簡化版,功能也會弱一些,它不能創建對象,也不能調用Java層的方法。
總之,“在虛擬機內部”是KNI所有特點的根源,記得這一點,KNI的所有內容都非常容易理解。
下文各節對KNI的各個方面做一下介紹,只詳述那些KNI所特有的內容,更全面的內容可以參考KVM附帶的KNI specification。
2. 數據類型:
2.1 原始類型:

上表中間一列是KNI所提供的8種原始類型,它們的長度與所對應的Java原始類型的長度相同。
2.2 對象類型:

上圖是KNI所支持的對象類型,其實所有對象都可作為jobject,只是對圖中所示的這些object類的子類有特別的支持,比如為數組類提供了操作數組元素的方法。
2.3 返回類型:

“返回類型”也就是本地方法的返回值的類型,KNI對它們有專門的定義。
上表右邊一列即本地方法的返回類型。
2.4 字符串類型、類描述符、字段描述符:
這三項內容都是在本地方法中對于Java層對象的描述,比如用”[Ljava/lang/String;”來描述String數組,這些內容與JNI規范以及Java虛擬機規范中所定義的都完全一致,所以這里不再多說。
3. KNI函數
本節分類簡介各種KNI函數的功能。大部分的函數功能比較容易理解,只有“參數傳遞”和“句柄操作”是比較特別的內容,將作詳細講解。
3.1 版本信息:
得到KNI的版本號。
3.2 類和接口操作:
初始化一下指向某種對象的名柄,對象名字在name中。
取得超類的句柄。原類的句柄在classHandle中,調用后超類的名柄將被存放在superclassHandle中。如果classHandle指向一個java.lang.Object對象,則superclassHandle應為NULL。
判斷classHandle1類的對象是否能安全轉換為classHandle2類的對象。
3.3 異常:
拋異常,name是異常類的名字,message是所附帶的信息。
出現致命錯誤時使用,向標準輸出打印出錯信息,并終止虛擬機。
3.4 對象操作
取得某對象所對屬的類,objectHandle是對象句柄,調用后,類句柄將被存入classHandle中。
判斷句柄objectHandle所指向的對象是否是句柄classHandle所指向的類的實例。
3.5 對象字段操作
取得類classHandle中由name和signature所指定的字段名。這個字段名的作用與JNI中的相同,是于在其它函數中讀寫字段的值。
上面兩個方法分別用于讀寫<Type>類型字段的值,<Type>為基本類型。
上面兩個方法分別用于讀寫對象。
3.6 靜態字段操作
取得靜態字段名。
上面兩個方法分別用于讀寫<Type>類型靜態字段的值,<Type>為基本類型。
上面兩個方法分別用于讀寫靜態對象。
3.7 字符串操作
取得字符串長度。
讀取字符串內容。
使用Unicode序列創建String。
使用UTF-8序列創建String。
3.8 數組操作
取得數組長度。
取得<Type>類型數組元素。
設置<Type>類型數組元素。
取得對象數組元素。
設置對象數組元素。
以字節為單位讀取一個區域的值。
以字節為單位設置一個區域的值。
3.9 參數傳遞
KNI的參數傳遞方式有一些不同的地方,在進行KNI調用時,從Java層傳來的參數在本地函數中不能直接讀取到。所有本地的函數,不論在Java層聲明時有多少個參數,它的參數表都將為空。這是因為KNI在虛擬機的內部,虛擬機在調用Java方法的時候是以堆棧的形式傳參的,在調用KNI方法的時候也是用了之種方式。
好在KNI的設計原則之一是“與虛擬機細節相隔離”,所以KNI的使用者不必去學習虛擬機的堆棧式傳參,KNI已經封裝好了一些方便使用的函數。
同樣,函數的返回值也要用專門的函數來傳回。
取得類型為<Type>的第index個參數,index是此參數在Java方法參數表中的位置。
比如:void native func (int i, long l, int j);這個Java方法有三個參數,在它的本地方法中,要得到它的第一個參數就要做如下調用:
KNI_GetParameterAsInt(1);
注意,long和double型的參數比較長,要占兩個位置,所以最后一個參數的序號不是3而是4。
同理,本函數讀取index處的對象,并使用toHandle句柄來索引它。
讀取當前類的this對象的句柄。
讀取當前類的句柄。
用于void型函數的返回。
用于<Type>型函數的返回。
3.10 名柄操作
KNI中對于Java傳來的對象的引用,不能直接使用C語言的指針,這是因為KNI在虛擬機內部,C層的函數中所引用的對象也還是會參與垃圾收集,隨時有可能被移動,C層的指針指向的是絕對的內存位置,就會失效。所以KNI使用“句柄”來索引對象,句柄由虛擬機來維護,不會失效。
聲明在當前函數中所要使用的句柄數,要使用多少句柄,都需要事先聲明。
聲明一個句柄。
判斷句柄是否為空。
判斷兩句柄是否指向了同一個對象。
釋放句柄。
刪除句柄。
刪除句柄并把句柄所指向的對象作為函數值返回。
4. 實例:
以下是一個使用KNI擴展KVM的例子。
4.1 從http://www.sun.com/software/communitysource/j2me/cldc/download.xml下載KVM源程序;
4.2 java類庫源代碼放在j2me_cldc/api/src,在其中增加類rayman.test.KNITest,源文件如下:/* rayman/test/KNITest.java */
package rayman.test;
public class KNITest {
public void PRintln(String s) {
System.out.println(s);
}
public static native int nativeTest(int i, int j);
}
4.3 部分C層源文件放在j2me_cldc/kvm/VmCommon/src,新建文件nativeTest.c:/* nativeTest.c */
#include <kni.h>
#include <stdio.h>
KNIEXPORT KNI_RETURNTYPE_INT Java_rayman_test_KNITest_nativeTest() {
jint i1 = KNI_GetParameterAsInt(1);
jint i2 = KNI_GetParameterAsInt(2);
printf("in function Java_rayman_test_KNITest_nativeTest() ");
KNI_ReturnInt(i1+i2);
}
4.4 在j2me_cldc/kvm/VmUnix/build/Makefile中加入nativeTest.c。
4.5 在j2me_cldc/build/linux下執行make USE_KNI=true,編譯好的可執行文件kvm就在j2me_cldc/kvm/VmUnix/build下。
4.6 編寫測試類Hello:/* Hello.java */
import rayman.test.*;
public class Hello {
public static void main(String [] args) {
KNITest t=new KNITest();
t.println("Hello KVM ! " + t.nativeTest(1,2));
}
}
設置好classpath并執行,得到如期結果!
另外,在KNIspec.pdf文檔中有很多例子可供參考。
進入討論組討論。(出處:http://m.survivalescaperooms.com)
新聞熱點
疑難解答