使用JNI和Reflect实现Object向void*的自动转换之三:学习 [原]

来源:互联网 发布:两院院士 知乎 编辑:程序博客网 时间:2024/05/29 07:58

三、准备:认识它,学习它,理解它,然后改造它

好,我们开始着手做,先起个工程名称,叫做Project JPointer吧。我们应该明确,所要做的是使用C++javaObject类型转换成void*的一块内存区。

问题很快就来了。

问题一,转换哪些数据,Object的所有成员还是只有public域?或者其他的选择。

问题二,java的数据成员如何对应到c语言中去。结合例子来说,Rectbottom要对应RECTbottom,这个如何保证?

问题三,java的数据类型如何对应到C中去。比如Javaint32位的,是否直接对应到C语言的int? java的基本数据类型的平台无关性如何保证?数据成员是个Object又如何对应?如果是数组哪?

隐隐约约的感到,还有一些C语言的基本问题,比如说结构体对齐等。

 

这些问题中,一些是要做抉择的,一些则是要研究的。比如说第一个问题,就是个策略选择问题。这里,我们选择了包装Object中的非静态数据成员。这样选择,是为了通常的需要。因为这和C++的内存安排比较接近。C++的静态数据成员是和非静态数据成员分开的。所以,我们的第一反应就是这种包装。尽管你反驳说包装private是不符合面向对象的,但是我们是为了做实际有用的软件设备,如果用户使用我们的设备,就应该明白这种策略。当然,成功的开发了这种策略的设备后,我们当然也可以仿照着做不同策略的设备。

第二个问题,java的数据成员以何种顺序映射到C的结构体中。我们的第一反应是名称对应策略。比如说那个JavaRectC的结构体RECT的对应,让Rectbottom对应了RECTbottom,而不管两者的声明和定义顺序如何。然而,这种方法是不可行的,因为C程序被编译后不会保存变量的名称,名称对照也就无从谈起。退而求其次,我们可否使用顺序映射策略哪?这就要研究了,C语言没问题,结构体内存的安排是按照声明的先后来做的,javaObject中数据成员有没有什么顺序哪?声明的顺序是否可由固定的算法保证的哪?这句话的意思反映到Rect上是说,由于left声明了在前,right在后,通过反射机制得到的域数组(Field[])是否也是这个顺序,如果是这个顺序,就可以采用这种顺序映射策略了。

首先查文档,看看java.lang.Class怎么说的。对于域的操作,一个比较有用的是

public Field[] getDeclaredFields() throws SecurityException

而文档中明确说明:The elements in the array returned are not sorted and are not in any particular order.这句话很打击人,让我们的顺序映射策略几乎破产。我们应该就此放弃这个方法吗?不见得,应该动手做做试试,看看顺序如何。

这个实验很简单,就是自己构造一些类,使用这个函数打印出来域的名称。很好,不管我怎么构造,总是按照声明的顺序依次打印出来。比如下面的:

public void testFieldsSequence(){

              Object obj = new Object(){

                     public int first;

                     private Object second;

                     protected double third;

              };

              Class clz = obj.getClass();

              Field[] fs = clz.getDeclaredFields();

              for(int i = 0; i < fs.length; i++){

                     System.out.println(fs[i].getName());

              }

       }

       输出结果是:

first

second

third

this$0

这个结果还是比较理想的,表明文档中说的不一定可信。我想写文档的人是为了保证以后留余地的原因吧,也可能不同的虚拟机有不同的机制。这个没有深入研究。但是,做得很多试验,发现Field[]的顺序总是和声明的顺序一致,即使是static数据成员也是如此。

这里,我们应该注意到this$0这个尾巴,由于是局部匿名类,才产生了这个数据成员,不过考虑到这个数据成员是追加到最后的,只是扩充对象的大小,反映到C的结构体中,使得C的结构体变大,不会影响以前的数据,所以,只要不改变这个值,就不用太在意。

好的,第二个问题基本有了答案,我们就采用顺序映射策略。

第三个问题,是关于基本数据类型的映射大小问题。在java平台下,基本数据类型的长度是固定的,比如说int32位的,double64位的,这些类型要忠实的表达到依赖于机器的c语言环境中,我们就要考虑数据长度问题了。Javaint类型,在JNI中定义为jint,而jint被定义为c语言的long,就反映了这个问题。所以,基本类型我们可以有如下安排表格:

Java类型

对应的C类型

字长(单位Byte)

boolean

unsigned char

1

byte

signed char

1

short

short

1

char

unsigned short

2

int

long/__int32

4

long

__int64

8

float

float

4

double

double

8

好,这些基本类型的字长都有了规定,我们就要考虑如果出现Object数据成员,如何安排到C的结构体中去,映射成什么类型?应该让一个类去寄存和解释Object去,所以,应该安排一个类的指针给他,比如说上面的second对象,就应该对应一个JObjectPtr*指针。这个JObjectPtr是包装了Object的对象。不过,应该清楚,指针终究是指针,不管是void*还是JObjectPtr*,只要是在Win32的环境下,大小都是32位的。所以,对于C结构体来说,成员变量为Object对应的类型,我们分配空间的时候,仍然只是四个字节,只不过解释它为JObjectPtr*而已。

好,还有数组没有打算好。应该认识到,java的数组和C的数组不同,倒是和C的指针很相似。考虑到实际用途中,我们可以把数组当成一个成员数据类型相同的结构体,比如说如下的int rcArray[4],和RECT结构体是一样的,反映到内存中都是4个整型数依次排列。出于实际的需要,我们考虑这样包装数组。

 

剩余的问题,比如说C的结构体对齐问题,我们可以在开发过程中解决,因为我们还不太明白这些细节是利是弊。

原创粉丝点击