原创 KNI 扩展实例

2011-3-4 16:16 1892 6 6 分类: MCU/ 嵌入式

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 版本信息:

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

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
6
关闭 站长推荐上一条 /3 下一条