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

来源:互联网 发布:两院院士 知乎 编辑:程序博客网 时间:2024/06/15 11:19

四、实现:使用Composite复合设计模式来实现

       我们打算使用C++来实现这个设备,这个工程名为JPointer,它的预期使用方式我们前面说过了,需要再明确一下“持有”这个概念。持有,是指C++中持有对Java层的某个实体的引用。在JNI提供的接口中,类型区别十分明显,每种类型都有相应的操作函数,setIntFieldsetDoubleField这样的区别,虽然保证了类型安全,但是对于我们实现这个不依赖于类型的软件设备来说,却是个麻烦事。如果有setField这样的函数,我们就不用再关心这些类型区别了,当然,让系统提供这样的结构是不可能的。

       由于存在着诸多的类型不同,我们的系统需要有一个体系结构。“持有”这个概念,然我们想到了不同类型需要不同的处理策略。但是,不同类型的处理,总是为了管理它所认识的对象,所以,“持有”就是共同点,所不同的就是不同的类型这个具体点。所以,自然而然的,我们想到了C++的虚函数来抽象这个“持有”概念。这个接口可以如下表达:

class JPtr

{

public:

     virtual int        getSize()const;                 //得到内存块大小

     virtual int        getMemberNum() = 0;              //得到数据成员的数目

     virtual void       setBuffer(void* newData) = 0;   //设置数据

     virtual void*      getBuffer()const=0;              //得到缓冲区指针

     virtual bool       isChanged()const = 0;            //缓冲区的数据是否改变

     virtual void       updateObject() = 0;             //从缓冲区中回滚一个对象,覆盖原来的对象

     virtual jvalue     getValue() = 0;                  //得到实体引用

     virtual void       setValue(jvalue val) = 0;        //设置新值

     virtual Type       getType() = 0;                   //得到被包装对象的类型

     virtual ~JPtr(){}

}

这个共同的接口,就表达“持有”的概念了。

再接着设计,我们看到,如果存在数据成员是一个Object类型,又需要对其成员进行包装,这就是一个典型的树形结构。而特殊的叶节点是形如int,double之类的Primitive类型。

所以,想到了复合设计模式(Composite Design Pattern)。从而,很快可以画出整个体系结构来。 

 

 

这个设计模型就这样出炉了,使用了C++的继承机制,我们最终是为了得到JObjectPtr这个软件设备,其他的类都是为了这个类服务的。这样的安排,还是为了隔离类型的差异,使得每一种类型的开发就很方便。

应该明确,基本数据类型对应的指针类,比如IntPtr的意思是持有的一个javaint数据,而他并不是真正的指针,它负责管理一个java中的int的值的更新提取,当然还有内存分配回收策略。

很快,我们就可以实现IntPtr,比如说它的大小是4字节,初始化的时候,给它分配4字节的内存块,然后保存下来缓冲区,然后当需要GetBuffer的时候,返回之。析构函数就是放掉内存,当接收到更新updateObject消息的时候,就是用int的一套JNI操作函数来执行。具体的可见源代码。

同样,我们实现了其他的基本数据类型的包装类。开发时,先实现IntPtrDoublePtr类型,然后转到JObjectPtr的开发来。

这个JObjectPtr是关键,我们如何来做它哪?首先,它的构造函数中就要生成缓冲区,算是资源初始策略吧。

       生成缓冲区的算法如下:

 

接受一个jobject类型,

根据javareflect来得到Object的数据成员,从而得到Field[]

/*第一次循环,解析成员数据*/

For each Field

得到域的访问域,

       if 是静态类型

         continue;

得到域的名称,并转换名称为C字符串

通过这个字符串得到FieldID

/*这样做看起来很迂回,而不能使用reflect来直接get/set数据域,这是因为访问控制的原因,反射机制对于privateprotected类型,限制直接访问的能力,但是JNI提供的系列函数get/set***Filed,是不管访问域的,所以,我们钻了这个一个空子,从而实现了私有成员的包装。J*/

       根据域id,得到域的类型,根据类型调用指针构造函数,返回JPtr*

JPtr* 入链

              End for

              /*第二次循环计算C结构内存块大小*/

              int bufferLength = 0;

for each jptr

                     bufferLength += jptr.getSize()

              end for

              /*分配内存*/

              buffer = new byte[bufferLength]

              /*拷贝数据*/

              byte* position = buffer;

/*构造缓冲区*/

              for each jptr

                     memcpy(position,jptr->getBuffer(),jptr-> getSize());

                     position += jptr-> getSize();

              end for

             

这个过程的详细实作,参见源代码。

updateObject的过程中,也有这个类似的过程。

应该指出,对于数组类型,我们应该做特殊的处理,当然过程和上面的算法类似。