`
阿尔萨斯
  • 浏览: 4186097 次
社区版块
存档分类
最新评论

应用程序框架设计(3):RuntimeClass与序列化

 
阅读更多
<iframe align="center" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog.html" frameborder="0" width="728" scrolling="no" height="90"></iframe>

SW系统的根是SObject,顾名思义是对普遍意义上的对象的抽象。其主要的支持有:

  • 运行时刻类信息(RuntimeClass)

    运行时刻类信息是经典程序结构中一个极其重要的部分。MFC、VCL、OWL、TurboVision都支持运行时刻类信息。它可能也是经典Object类中唯一比较实用的东西。而同时它也是Object类最容易让人感到迷惑的地方。简单地说,运行时刻类信息主要有两个用途:

    a)创建对象
    b)确定对象的类型

    其实RuntimeClass的实现机制一点也不神秘。它无非是通过类注册方式将类名与其父类、实例创建函数联系起来。

    SW系统的运行时刻类信息定义为:
    typedefSObject*(*FNBUILDER)();
    structSRuntimeClass
    {
    LPCTSTRm_lpszClassName;
    //类名
    SRuntimeClass*m_lpBaseClass;//父类
    FNBUILDERm_fnCreator;//实例创建
    SRuntimeClass*m_lpPrevClass;
    };

    每一个SW系统的类都对应有一个SRuntimeClass实例来描述该类。可以用__typeid(Class)来找到定位该类RuntimeClass信息。此宏与MFC的RUNTIME_CLASS(Class)宏完全相同。

    注意到SRuntimeClass有一个成员m_lpPrevClass,它只是用于在系统维护RuntimeClass链表。从而可以让用户通过类的名字来查询一个类的运行时刻类信息,并且创建该类的对象。例如下面的序列化技术就需要这样做。

    SW系统的运行时刻类信息相关函数和宏主要有:

    __typeid(Class)//定位一个类的运行时刻类信息
    DECLARE_CLASS(Class)//声明一个类
    IMPLEMENT_CLASS(Class,BaseClass)//注册一个类

    SObject
    * gCreateObject(LPCTSTRszClassName);
    //根据一个类的类名,创建该类的一个实例
    constSRuntimeClass* SObject::GetRuntimeClass()const;
    //确定一个对象的RuntimeClass信息
    BOOLSObject::IsKindOf(constSRuntimeClass*pRC)const;
    //判断一个对象是否是pRC描述的类或者其派生类的实例

    它们的实现代码相当简单,不详细解释:

    #define__typeid(Class)(&Class::x_theRuntimeClass)

    #defineDECLARE_CLASS(Class)
    constSRuntimeClass*Class::GetRuntimeClass()const;
    staticconstSRuntimeClassx_theRuntimeClass;
    staticSObject*x_CreateObject();

    #defineIMPLEMENT_CLASS(Class,BaseClass)
    constSRuntimeClass*Class::GetRuntimeClass()const
    {
    return__typeid(Class);
    }
    constSRuntimeClassClass::x_theRuntimeClass(
    _T(#Class),
    __typeid(BaseClass),
    Class::x_CreateObject
    );
    SObject
    *Class::x_CreateObject()
    {
    returnnewClass;
    }

    constSRuntimeClass*x_lpRuntimeClassListHead=NULL;

    SRuntimeClass::SRuntimeClass(
    LPCTSTRszClassName,
    constSRuntimeClass*lpBaseClass,
    FNBUILDERfnCreator)
    {
    m_szClassName
    =szClassName;
    m_lpBaseClass
    =lpBaseClass;
    m_fnCreator
    =fnCreator;
    m_lpPrevClass
    =x_lpRuntimeClassListHead;
    x_lpRuntimeClassListHead
    =this;
    }

    SObject
    *gCreateObject(LPCTSTRszClassName)
    {
    constSRuntimeClass*pRC=x_lpRuntimeClassListHead;
    for(;pRC;pRC=pRC->m_lpPrevClass)
    if(!_tcscmp(szClassName,pRC->m_szClassName))
    returnpRC->fnCreateObject();
    returnNULL;
    }

    BOOLSObject::IsKindOf(
    constSRuntimeClass*pBaseClass)const
    {
    SRuntimeClass
    *pRC=this->GetRuntimeClass();
    for(;pRC;pRC=pRC->m_lpBaseClass)
    if(pRC==pBaseClass)
    returnTRUE;
    returnFALSE;
    }
  • 序列化(Serialization)

    序列化是建立在运行时刻类信息(RuntimeClass)之上的一个应用。所谓序列化是指通过一个自动化机制将对象保存到磁盘,或者将对象从磁盘读出来。尽管序列化有种种的缺点,但不得不承认它是对面相对象思想的一个经典运用。如果你的程序支持序列化,那么存盘的过程你唯一要做的就是,你对应用程序说,“存盘!”而后应用程序就可以将自己保存到磁盘上。你对应用程序说,“读盘!”而后应用程序就会从磁盘中读入数据重建对象。一切就这么简单!

    但是序列化是有严重缺陷的。它没有在兼容性、容错性上的保证。所以它比较适合于保存不被推广的文件格式,例如程序配置,它们可能不必考虑兼容性问题。但在对用户数据的保存上,它应该只是一个理论上的成果。所以尽管Microsoft提供了序列化,但是他自己从来都不会使用序列化去保存数据文件。

    SW系统的序列化实现上与MFC完全一致。但它是在当我还在还没有接触MFC时就已经实现的一个技术。这种一致性应该说是对面向对象思想把握的必然结果。但前提是你掌握了RuntimeClass(在实现RuntimeClass上,我主要借鉴了TurboVision中的实现方式。但是对其进行了大量的简化)。

    面向对象的基础思想是,当你要一个对象做一件事时,你只要向它发送相应的消息,而不必关心对象如何完成此任务。同样地,在接收到用户的存盘消息后,你只是简单地向应用程序发送存盘消息。而应用程序在保存完私有数据后继续向其所有子对象发送存盘消息,这个过程一直延续到简单对象。从而完成存盘动作。当然,为了支持序列化,SObject类要加虚函数:
    virtual Serialize(SArchive &ar);
    其中SArchive类是流操作类。与C++的流操作类基本类似,但它是二进制流。

    在实现序列化中,实现写盘是简单的:
    HRESULT SArchive::WriteObject(SObject *pOb);

    主要需要解决的问题时读盘时对象的重建上。一般地,读盘时会有这样两种需求:

    a)需要读取的对象已经被分配内存(即对象已经存在)。对应的读盘函数为:
    HRESULT SArchive::ReadObject(SObject *pOb);

    b)对象的类型未知,从而对象不可能预先创建。对应读盘函数为:
    HRESULT SArchive::ReadObject(
    const SRuntimeClass *pClassRequest,
    SObject **ppOb);

    情形a)是比较简单的,它不需要RuntimeClass的支持。MFC中没有提供此函数。现考虑情形b)。为了能够从磁盘重建对象,显然应该将一个描述对象的id值与对象的创建函数关联。这已经由RuntimeClass技术完成了。在对象id的选取上,SW系统与MFC都使用了对象的类名。但是这不是唯一的选择。这一点在后面的COM技术部分还会提到。不管怎样,我们可以说,为了能够从磁盘中重建对象,需要在保存对象具体数据之前先保存对象的类信息(RuntimeClass)。读盘时先读出类信息,由此重建对象实例。代码如下:
    voidSArchive::WriteObject(SObject*pOb)
    {
    WriteString(pOb
    ->GetRuntimeClass()->m_szClassName);
    pOb
    ->Write(*this);
    }

    对于情形a)的ReadObject:
    HRESULTSArchive::ReadObject(SObject*pOb)
    {
    LPCTSTRlpsz
    =ReadString();
    if(!_tcscmp(pOb->GetRuntimeClass()->m_szClassName,lpsz))
    {
    delete[]lpsz;
    pOb
    ->Read(*this);
    returnS_OK;
    }
    delete[]lpsz;
    returnE_FAIL;
    }

    对于情形b)的ReadObject:
    HRESULTSArchive::ReadObject(
    constSRuntimeClass*pClassReq,
    SObject
    **ppOb)
    {
    LPCTSTRszReadedClass
    =ReadString();
    *ppOb=NULL;
    if(pClassReq)
    {
    if(!_tcscmp(szReadedClass,pClassReq->m_szClassName))
    *ppOb=pClassReq->m_fnCreator();
    }
    else
    {
    *ppOb=::gCreateObject(szReadedClass);
    }
    delete[]szReadedClass;
    if(*ppOb)
    {
    (
    *ppOb)->Read(*this);
    returnS_OK;
    }
    returnE_FAIL;
    }

    在此基础上,序列化中的另一个问题是,对象间的循环引用问题。在一个复杂的数据结构中,往往有A对象引用B对象,同时B对象又引用A对象。此时用上面的序列化机制,就会出现死循环。MFC以一种巧妙的方式解决了此问题。其核心思想是引入一个Hash表,将已经保存(或即将要保存)的对象指针与对象id(由系列化机制分配)联系起来。在第二次保存此对象时只是保存对象id,这样也就中断了循环。从这一个角度讲,序列化是相当优秀的。它可以轻易地保存任何一种复杂的数据结构。

    考虑了循环引用问题后的对象读写函数如下:
    voidSArchive::WriteObject(SObject*pOb)
    {
    WriteString(pOb
    ->GetRuntimeClass()->m_szClassName);
    UINTnObIndex
    =-1;
    if(x_pMap->Lookup(pOb,&nObIndex))
    {
    //如果对象已经保存!
    *thisnObIndex;
    }
    else
    {
    *this(UINT)-1;
    //规定-1表示对象是正常存盘
    x_pMap->SetAt(pOb,::gGetObIndex(pOb));
    //这里gGetObIndex是系统为pOb对象分配id号的函数
    //MFC中只是简单地用一个递增编号而已。
    pOb->Write(*this);
    }
    }

    HRESULTSArchive::ReadObject(
    constSRuntimeClass*pClassReq,
    SObject
    **ppOb)
    {
    *ppOb=NULL;
    LPCTSTRszReadedClass
    =ReadString();
    UINTnObIndex
    =-1;
    *this>>nObIndex;
    if(nObIndex!=(UINT)-1)
    {
    //已经被保存过的情形!!!
    if(pClassReq&&
    _tcscmp(szReadedClass,pClassReq
    ->m_szClassName))
    {
    delete[]szReadedClass;
    returnE_FAIL;
    }
    delete[]szReadedClass;
    *ppOb=::gGetObByIndex(nObIndex);
    returnS_OK;
    }
    //否则,正常情形,同以前一样!!!
    if(pClassReq)
    {
    if(!_tcscmp(szReadedClass,pClassReq->m_szClassName))
    *ppOb=pClassReq->m_fnCreator();
    }
    else
    *ppOb=::gCreateObject(szReadedClass);
    delete[]szReadedClass;
    if(*ppOb)
    {
    (
    *ppOb)->Read(*this);
    returnS_OK;
    }
    returnE_FAIL;
    }



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics