KVM (K Virtual Machine) 是 J2ME CLDC 參考實作的核心 VM,體積小、可移植性強,適合嵌入式裝置。本文記錄在 Windows + Cygwin + VC6 環境下,從原始碼編譯 KVM、新增系統呼叫 API、並以標準 KNI (KVM Native Interface) 擴充功能的完整流程。
| 工具 | 版本/來源 | 用途 |
|---|---|---|
| J2ME CLDC 原始碼 | Sun 官網 (cldc 1.1) | KVM 核心碼、類庫、建構腳本 |
| Cygwin | cygwin.com (含 gcc, make, grep, find) | 提供 Unix-like 建構環境 |
| JDK | 1.4.2 / JBuilder 2005 附帶 | javac 編譯類庫、JCC 工具運行 |
| VC6 | Visual C++ 6.0 | 編譯連結最終 kvm.exe |
# 1. 設定 PATH (Cygwin bin 優先,避免 Windows find.exe 衝突)
export PATH=/cygdrive/d/cygwin/bin:/cygdrive/d/JBuilder2005/jdk1.4/bin:$PATH
# 2. 進入建構目錄
cd /cygdrive/d/J2ME_cldc/build/win32
# 3. 執行 make (會依序:javac 編譯 api/ → 打包 classes.zip → JCC 產生 ROMJavaWin.c/nativeFunctionTableWin.c → gcc 編譯 C 碼 → 產生 .o)
make
# 4. 若報 "Java 找不到",確認 Cygwin bin 在 PATH 最前端
# 5. 編譯成功產生 VmWin.exe / kvm.exe
# 6. 測試:無需 preverify,直接運行
kvm -cp . HelloWorld
make (產生 JCC 產出檔)。J2ME_cldc/kvm/VmWin/build/kvm.dsw。kvm.exe (約 200KB,含 CLDC 核心類庫)。// 新增檔案:J2ME_cldc/api/org/test/MiniTest.java
package org.test;
public class MiniTest {
public static native int TestInt(); // 回傳 9999
}
執行 make,連結階段會報 undefined symbol _Java_org_test_MiniTest_TestInt,此為預期現象。
nativeCore.c 實作 Native 函式#include "kni.h" // 若使用 KNI 輔助 API
#include "nativeCore.h"
void Java_org_test_MiniTest_TestInt(void) {
// KVM 運算棧為 cell 型別,pushStack 宏將 int 壓入棧頂作為返回值
pushStack(9999);
}
import org.test.MiniTest;
public class Test {
public static void main(String[] args) {
System.out.println("Result = " + MiniTest.TestInt()); // 輸出 9999
}
}
javac -cp ../classes.zip Test.java
kvm -cp . Test
KNI 提供型別安全、例外處理、物件存取等完整 API,對應 JNI 但無動態載入 DLL 能力 (KVM 燒錄 ROM)。
Java KNI (C)
boolean jboolean (unsigned char)
byte jbyte (signed char)
char jchar (unsigned short)
short jshort (short)
int jint (long)
float jfloat (float)
double jdouble (double)
Object jobject (cell**)
Class jclass (jobject)
String jstring (jobject)
int[] jintArray (jarray)
KNI_FindClass(const char*, jclass*)KNI_GetFieldID(jclass, const char* name, const char* sig)KNI_GetIntField(jobject, jfieldID) / KNI_SetIntField(...)KNI_NewObject(jclass, jmethodID)KNI_ThrowNew(jclass, const char* msg)// Java 端
package org.test;
public class KniDemo {
static int counter = 0;
public static native void inc();
}
// C 端 (nativeCore.c)
#include "kni.h"
void Java_org_test_KniDemo_inc(void) {
jclass cls;
KNI_FindClass("org/test/KniDemo", &cls);
jfieldID fid = KNI_GetFieldID(cls, "counter", "I");
jint val = KNI_GetStaticIntField(cls, fid);
KNI_SetStaticIntField(cls, fid, val + 1);
}
| 現象 | 原因 | 解法 |
|---|---|---|
| make 階段找不到 javac | JDK bin 未在 PATH | export PATH 含 JDK bin |
| Windows find.exe 衝突 | 系統 PATH 靠前 | Cygwin bin 置 PATH 最前 |
| VC6 專案找不到 ROMJavaWin.c | 未先跑 make 產生 JCC 輸出 | 必須先 make 成功 |
| Native 回傳值錯誤 | 未正確操作運算棧 | 改用 KNI API 或參考現有 nativeCore.c 範例 |
| KNI 編譯找不到 kni.h | include 路徑未加 | 專案屬性 → C/C++ → Additional Include 加入 KVM 根目錄 |
掌握 KVM 編譯鏈與 KNI 擴充,即可為嵌入式 Java 裝置注入專屬硬體存取、專有協議、高效能演算法等能力。建議優先使用 KNI 而非直接操作運算棧,代碼更安全、可維護,且符合 Sun 規範。