服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java基础知识:kvm的扩展

    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 原始类型:

java基础知识:kvm的扩展(图一)

    上表中间一列是kni所提供的8种原始类型,它们的长度与所对应的java原始类型的长度相同。

    2.2 对象类型:

java基础知识:kvm的扩展(图二)

    上图是kni所支持的对象类型,其实所有对象都可作为jobject,只是对图中所示的这些object类的子类有特别的支持,比如为数组类提供了操作数组元素的方法。

    2.3 返回类型:

java基础知识:kvm的扩展(图三)

    “返回类型”也就是本地方法的返回值的类型,kni对它们有专门的定义。

    上表右边一列即本地方法的返回类型。

    2.4 字符串类型、类描述符、字段描述符:

    这三项内容都是在本地方法中对于java层对象的描述,比如用“[ljava/lang/string;”来描述string数组,这些内容与jni规范以及java虚拟机规范中所定义的都完全一致,所以这里不再多说。

    3. kni函数

    本节分类简介各种kni函数的功能。大部分的函数功能比较容易理解,只有“参数传递”和“句柄操作”是比较特别的内容,将作详细讲解。

    3.1 版本信息:

    3.1.1 jint kni_getversion()

    得到kni的版本号。

    3.2 类和接口操作:

    3.2.1 void kni_findclass(const char* name, jclass classhandle)

    初始化一下指向某种对象的名柄,对象名字在name中。

    3.2.2 void kni_getsuperclass(jclass classhandle, jclass superclasshandle)

    取得超类的句柄。原类的句柄在classhandle中,调用后超类的名柄将被存放在superclasshandle中。如果classhandle指向一个java.lang.object对象,则superclasshandle应为null.

    3.3.3 jboolean kni_isassignablefrom(jclass classhandle1, jclass classhandle2)

    判断classhandle1类的对象是否能安全转换为classhandle2类的对象。

    3.3 异常:

    3.3.1 jint kni_thrownew(const char* name, const char* message)

    抛异常,name是异常类的名字,message是所附带的信息。

    3.3.2 void kni_fatalerror(const char* message)

    出现致命错误时使用,向标准输出打印出错信息,并终止虚拟机。

    3.4 对象操作

    3.4.1 void kni_getobjectclass(jobject objecthandle, jclass classhandle)

    取得某对象所对属的类,objecthandle是对象句柄,调用后,类句柄将被存入classhandle中。

    3.4.2 jboolean kni_isinstanceof(jobject objecthandle, jclass classhandle)

    判断句柄objecthandle所指向的对象是否是句柄classhandle所指向的类的实例。

    3.5 对象字段操作

    3.5.1 jfieldid kni_getfieldid(jclass classhandle, const char* name, const char* signature)

    取得类classhandle中由name和signature所指定的字段名。这个字段名的作用与jni中的相同,是于在其它函数中读写字段的值。

    3.5.2 <nativetype> kni_get<type>field(jobject objecthandle, jfieldid fieldid)

    3.5.3 void kni_set<type>field(jobject objecthandle, jfieldid fieldid, <nativetype> value)

    上面两个方法分别用于读写<type>类型字段的值,<type>为基本类型。

    3.5.4 void kni_getobjectfield(jobject objecthandle, jfieldid fieldid, jobject tohandle)

    3.5.5 void kni_setobjectfield(jobject objecthandle, jfieldid fieldid, jobject fromhandle)

    上面两个方法分别用于读写对象。

    3.6 静态字段操作

    3.6.1 jfieldid kni_getstaticfieldid(jclass classhandle, const char* name, const char* signature)

    取得静态字段名。

    3.6.2 <nativetype> kni_getstatic<type>field(jclass classhandle, jfield fieldid)

    3.6.3 void kni_setstatic<type>field(jclass classhandle, jfieldid fieldid, <nativetype>value)

    上面两个方法分别用于读写<type>类型静态字段的值,<type>为基本类型。

    3.6.4 void kni_getstaticobjectfield(jclass classhandle, jfield fieldid, jobject tohandle)

    3.6.5 void kni_setstaticobjectfield(jclass classhandle, jfield fieldid, jobject fromhandle)

    上面两个方法分别用于读写静态对象。

    3.7 字符串操作

    3.7.1 jsize kni_getstringlength(jstring stringhandle)

    取得字符串长度。

    3.7.2 void kni_getstringregion(jstring stringhandle, jsize offset, jsize n, jchar* jcharbuf)

    读取字符串内容。

    3.7.3 void kni_newstring(const jchar* uchars, jsize length, jstring stringhandle)

    使用unicode序列创建string.

    3.7.4 void kni_newstringutf(const char* utf8chars, jstring stringhandle)

    使用utf-8序列创建string.

   3.8 数组操作

    3.8.1 jsize kni_getarraylength(jarray arrayhandle)

    取得数组长度。

    3.8.2 <nativetype> kni_get<type>arrayelement(<arraytype>arrayhandle, jint index)

    取得<type>类型数组元素。

    3.8.3 void kni_set<type>arrayelement(<arraytype>arrayhandle, jint index, <nativetype> value)

    设置<type>类型数组元素。

    3.8.4 void kni_getobjectarrayelement(jobjectarray arrayhandle, jint index, jobject tohandle)

    取得对象数组元素。

    3.8.5 void kni_setobjectarrayelement(jobjectarray arrayhandle, jint index, jobject fromhandle)

    设置对象数组元素。

    3.8.6 void kni_getrawarrayregion(jarray arrayhandle, jsize offset, jsize n, jbyte* dstbuffer)

    以字节为单位读取一个区域的值。

    3.8.7 void kni_setrawarrayregion(jarray arrayhandle, jsize offset, jsize n, const jbyte* srcbuffer)

    以字节为单位设置一个区域的值。

    3.9 参数传递

    kni的参数传递方式有一些不同的地方,在进行kni调用时,从java层传来的参数在本地函数中不能直接读取到。所有本地的函数,不论在java层声明时有多少个参数,它的参数表都将为空。这是因为kni在虚拟机的内部,虚拟机在调用java方法的时候是以堆栈的形式传参的,在调用kni方法的时候也是用了之种方式。

    好在kni的设计原则之一是“与虚拟机细节相隔离”,所以kni的使用者不必去学习虚拟机的堆栈式传参,kni已经封装好了一些方便使用的函数。

    同样,函数的返回值也要用专门的函数来传回。

    3.9.1 <returntype> kni_getparameteras<type>(jint index)

    取得类型为<type>的第index个参数,index是此参数在java方法参数表中的位置。

    比如:void native func (int i, long l, int j);这个java方法有三个参数,在它的本地方法中,要得到它的第一个参数就要做如下调用:

    kni_getparameterasint(1);

    注意,long和double型的参数比较长,要占两个位置,所以最后一个参数的序号不是3而是4.

    3.9.2 void kni_getparameterasobject(jint index, jobject tohandle)

    同理,本函数读取index处的对象,并使用tohandle句柄来索引它。

    3.9.3 void kni_getthispointer(jobject tohandle)

    读取当前类的this对象的句柄。

    3.9.4 void kni_getclasspointer(jclass tohandle)

    读取当前类的句柄。

    3.9.5 void kni_returnvoid()

    用于void型函数的返回。

    3.9.6 void kni_return<type>(<nativetype> value)

    用于<type>型函数的返回。

    3.10 名柄操作

    kni中对于java传来的对象的引用,不能直接使用c语言的指针,这是因为kni在虚拟机内部,c层的函数中所引用的对象也还是会参与垃圾收集,随时有可能被移动,c层的指针指向的是绝对的内存位置,就会失效。所以kni使用“句柄”来索引对象,句柄由虚拟机来维护,不会失效。

    3.10.1 void kni_starthandles(n)

    声明在当前函数中所要使用的句柄数,要使用多少句柄,都需要事先声明。

    3.10.2 void kni_declarehandle(handle)

    声明一个句柄。

    3.10.3 jboolean kni_isnullhandle(handle)

    判断句柄是否为空。

    3.10.4 jboolean kni_issameobject(handle1, handle2)

    判断两句柄是否指向了同一个对象。

    3.10.5 void kni_releasehandle(handle)

    释放句柄。

    3.10.6 void kni_endhandles()

    删除句柄。

    3.10.7 void kni_endhandlesandreturnobject(jobject objecthandle)

    删除句柄并把句柄所指向的对象作为函数值返回。

   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文档中有很多例子可供参考。

扫描关注微信公众号