J2ME的KVM相关教程

编译KVM

  
KVM可以说是JVM中比较小而且比较原始的一个版本。KVM是一般放置在嵌入式设备,比如手机等资源有限的终端内运行。KVM没有采用HotSpot
JVM里面诸多优化技术,代码十分简单,有利于我们理解JAVA程序的执行过程。虽然KVM里面执行JAVA程序可能与现在PC上流行的JVM不太相同,
但是大致原理是相似的。
   下面是我在Windows编译KVM的过程。

1.下载KVM源代码

http://www.sun.com/software/communitysource/j2me/cldc/download.xml

现在有cldc1.1和cldc1.04的两个版本。差别不是很大。我选择的是1.1的版本的KVM源代码。

2.下载Cygwin

 
Sun提供的KVM虽然有VC6下编译的工程,但是整个编译过程还是有部分需要Cygwin的参与。Cygwin提供了Linux下的
make,gcc,grep,find等很多程序,这些程序在KVM里面的很多makefile里面都使用到了的。既然是Windows下,那么只有下载
cygwin了。

  安装cygwin的时候,记着把gcc编译
器,make,grep,find等常用的工具装进去。其实你也可以下载Dev-Cpp这个工具,里面带有cygwin的gcc,make但是没有
grep,find这些shell程序。所以还是应该下载一个cygwin。cygwin版本无所谓,很老的都可以。不过cygwin很大就是了。

  http://www.cygwin.org

 

3. 安装JDK

  这个过程就不用我多说了,我是直接安装的JBuilder2005。

4. make编译

     将j2me_cldc下载下来后,解压开。进入j2me_cldc\build\win32目录。里面有个makefile文件,这就是win32环境下的编译文件。

   整个过程需要在windows的命令提示符下完成,还需要设置PATH。在命令提示符下输入

   PATH=D:\CYGWIN\BIN;D:\Borland\JBuilder2005\jdk1.4\bin;%PATH%

   然后直接敲入make,就可以进行整个编译过程了。整个编译过程应该还是很顺利的。

   有可能出现"*.java 找不到"的错误,多半是因为Windows的command里面有个find.exe和cygwin\bin里面的那个find.exe冲突了,应该把cygwin\bin放在前面。

5. 在VC6下编译KVM

  
在j2me_cldc\kvm\VmWin\build下有个VC6的dsw工程文件,不过必须先经历上面的make编译后才能打开这个VC6工程文件进
行编译。因为整个KVM的编译需要的两个文件nativeFunctionTableWin.c和ROMjavaWin.c是在编译执行
tools\jcc后生成的,没有编译执行tools\jcc是不会有这两个文件的。

  
jcc是个将Java核心的class文件的bytecodes转换成一堆C语言的数组,然后让KVM编译的时候包含进去,这些核心class的
bytecodes就是放在上面两个文件里面。这样做的好处就是在KVM执行时不用在再去找核心的class文件然后装载。

6. 测试运行一下自己编译的KVM

   编译成功的话,会生成VmWin.exe或者kvm.exe文件。你可以测试一下写个helloworld.java,不需要preverifier,就可以直接运行你的helloworld.class的。

   SUN这个j2me
cldc的KVM里面已经提供了一个putchar的native function,你可以根据自己的喜好,去增加修改Java的native
function。整个KVM.exe有200多K,不过代码可能只有80K多点,其它的就是Java的CLDC核心class。

——————————————————-

增加KVM中的系统调用API

 大
家都晓得KVM是不支持Native函数调用的,如果要增加一些系统调用的API,那么只能加到KVM内部。同时,不同的J2ME设备,也有不同的系统调
用API以及它们的实现。我们从SUN那么下载到原始的KVM源代码,如何为其增加一个系统调用API呢?本文以具体实践的步骤一步一步来讲解增加KVM
系统API的方法。

  
其实为KVM增加一个系统调用API比为Linux增加一个系统调用API还简单。大致就分成两步工作就完成。一步是在classes.zip中增加一个
你自己新增的class,一步是在KVM的nativeCore.c中实现这个新增的class的native api函数。

   下面以org.test.MiniTest这个新增的class为例来实现一个TestInt()系统调用函数。函数的功能很简单,就是返回一个整数9999。

1. 新增org.test.MiniTest类

   从SUN那里下载到j2me_cldc 1.1版本的KVM代码后。在j2me_cldc\api目录下,增加org\test\MiniTest.java的包目录以及java文件。然后写上如下的代码:

package  org.test;
public class MiniTest
{
 public static native int TestInt();
}

2. 进行第一次编译

  
根据上一篇文章中的KVM编译方法,在命令提示符下,跳到目录j2me_cldc\build\win32下,输入make命令进行第一次整体编译。不
过,这次编译过程在编译连接KVM中的*.o文件的时候,会提示一个找到_Java_org_test_MiniTest_TestInt符号的错误提
示。

   

  这是因为在我们只是在org.test.MiniTest中定义了这个native函数TestInt,但是并没有在KVM的任何一个c文件中实现其对应的函数。

  
首先编译过程是用javac来编译j2me_cldc/api里面的所有的*.java文件,然后将其class文件打包成一个classes.zip,
然后JCC这个工具会默认根据classes.zip生成ROMJavaWin.c和nativeFunctionTableWin.c。而在
ROMJavaWin.c声明这个native函数:

   extern void Java_org_test_MiniTest_TestInt(void);

3. 实现Java_org_test_MiniTest_TestInt函数

  
从KVM中的代码可以看到,KVM默认都是把一些native函数放到了nativeCore.c这个文件里面。你也可以自己去新增一些C程序文件,不过
本例就把这个Java_org_test_MiniTest_TestInt放在了nativeCore.c文件。

   下面是代码:

void Java_org_test_MiniTest_TestInt(void)
{
 pushStack(9999);
}

 
这里为什么把返回值使用pushStack这个宏来返回的原因就不好说了,关于JAVA运行的方式其实就是一个堆栈,Java的字节码其实就是一种栈式语
言,这个在编译原理里面的中间代码生成那一章可以找到它的原型和其说明。再者,还可以看《Inside Java Virtual
Machine》这本书。

4. 第二次编译

  第二次编译就是可以生成真正的kvm.exe文件了。还是跟第一次编译以及上一篇编译KVM的方法一样,敲入make命令即可。

5. 测试MiniTest.TestInt这个API

   自己写了一个Test的类,来测试这个API:

import org.test.*;
class Test {
 public static void main(String[] args) {
  System.out.println("Test Result = "+MiniTest.TestInt());
 }
}

   用javac编译的时候,需要把前面的j2me_cldc/classes.zip拷贝过来,执行:

javac -classpath classes.zip Test.java

kvm -classpath . Test

——————————————————————

使用标准的KNI增加KVM的系统调用

 

SUN在
发布的KVM源代码中其实就已经提供KNI(K Native
Interface)这套比较标准的API扩展辅助库。KNI和JNI类似,只是KNI不能象JNI那样外带一个DLL文件,KVM通常都是要烧进嵌入式
设备的ROM里面的,所以不能装载KVM之外的本地代码。使用KNI来扩展KVM的方法与上一篇文章的方法类似,只是KNI提供了一套功能相对完善的接
口。

1. KNI中的数据结构

   KJAVA(或者叫J2ME)中的变量数据结构在KNI中有一套自己的对应的定义。

  下面是部分的对应。

 Java中的数据结构  KNI中C语言的数据结构
 boolean  jboolean
 byte  jbyte
 char  jchar
 int  jint

  可以在KVM中的kni.h文件找到这些数据类型的定义。

typedef unsigned char  jboolean;
typedef signed char    jbyte;
typedef unsigned short jchar;
typedef short          jshort;
typedef long           jint;
typedef float          jfloat;
typedef double         jdouble;
typedef long           jsize;

typedef FIELD   jfieldID;
typedef cell**  jobject;

typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray  jbooleanArray;
typedef jarray  jbyteArray;
typedef jarray  jcharArray;
typedef jarray  jshortArray;
typedef jarray  jintArray;
typedef jarray  jlongArray;
typedef jarray  jfloatArray;
typedef jarray  jdoubleArray;
typedef jarray  jobjectArray;

 

 

 Java中的数据结构

 KNI中C语言的数据结构

 boolean

 jboolean

 byte

 jbyte

 char

 jchar

 int

 jint

  可以在KVM中的kni.h文件找到这些数据类型的定义。

typedef unsigned char  jboolean;
typedef signed char    jbyte;
typedef unsigned short jchar;
typedef short          jshort;
typedef long           jint;
typedef float          jfloat;
typedef double         jdouble;
typedef long           jsize;

typedef FIELD   jfieldID;
typedef cell**  jobject;

typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray  jbooleanArray;
typedef jarray  jbyteArray;
typedef jarray  jcharArray;
typedef jarray  jshortArray;
typedef jarray  jintArray;
typedef jarray  jlongArray;
typedef jarray  jfloatArray;
typedef jarray  jdoubleArray;
typedef jarray  jobjectArray;

 

2.  KNI提供的辅助函数

   KNI为了方便大家为它的KVM编写扩展本地API,还专门编写了一套比较完善的辅助函数库。比如:

   KNI_FindClass(const char* name, jclass classHandle);

   KNI_GetObjectClass(jobject objectHandle, jclass classHandle);

   jfieldID KNI_GetFieldID(jclass classHandle, const char* name, const char* signature);

   jint     KNI_GetIntField(jobject objectHandle, jfieldID fieldID);

   这些函数都在kni.h与kni.c中声明与定义的。如果自己需要编写一些本地API函数,那么就可以直接#include "kni.h"来使用这些方便的辅助函数了。

   关于这些函数的说明文档,KNIspec.pdf也放在了j2me_cldc/docs里面,可以参考。

3.  利用KNI实现KVM的本地API

   利用KNI实现KVM的本地API的方法和前一篇文章讲述的基本一样。只是未来能够使用kni.h和kni.c中的KNI辅助函数,需要在你的native API函数的*.c文件中加上#include "kni.h",其它的就是一样的。

   在SUN提供的KNIspec.pdf文档的最后还列举了很多samples以及编译实现的步骤