SW系统的根是SObject,顾名思义是对普遍意义上的对象的抽象。其主要的支持有:
- 运行时刻类信息(RuntimeClass)
运行时刻类信息是经典程序结构中一个极其重要的部分。MFC、VCL、OWL、TurboVision都支持运行时刻类信息。它可能也是经典Object类中唯一比较实用的东西。而同时它也是Object类最容易让人感到迷惑的地方。简单地说,运行时刻类信息主要有两个用途:
a)创建对象
b)确定对象的类型
其实RuntimeClass的实现机制一点也不神秘。它无非是通过类注册方式将类名与其父类、实例创建函数联系起来。
SW系统的运行时刻类信息定义为:
typedef SObject * ( * FNBUILDER)();
struct SRuntimeClass
{
LPCTSTR m_lpszClassName; // 类名
SRuntimeClass * m_lpBaseClass; // 父类
FNBUILDER m_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(LPCTSTR szClassName);
// 根据一个类的类名,创建该类的一个实例
const SRuntimeClass * SObject::GetRuntimeClass() const ;
// 确定一个对象的RuntimeClass信息
BOOL SObject::IsKindOf( const SRuntimeClass * pRC) const ;
// 判断一个对象是否是pRC描述的类或者其派生类的实例
它们的实现代码相当简单,不详细解释:
#define __typeid(Class) (&Class::x_theRuntimeClass)
#define DECLARE_CLASS(Class)
const SRuntimeClass * Class::GetRuntimeClass() const ;
static const SRuntimeClass x_theRuntimeClass;
static SObject * x_CreateObject();
#define IMPLEMENT_CLASS(Class, BaseClass)
const SRuntimeClass * Class::GetRuntimeClass() const
{
return __typeid(Class);
}
const SRuntimeClass Class::x_theRuntimeClass(
_T(#Class),
__typeid(BaseClass),
Class::x_CreateObject
);
SObject * Class::x_CreateObject()
{
return new Class;
}
const SRuntimeClass * x_lpRuntimeClassListHead = NULL;
SRuntimeClass::SRuntimeClass(
LPCTSTR szClassName,
const SRuntimeClass * lpBaseClass,
FNBUILDER fnCreator)
{
m_szClassName = szClassName;
m_lpBaseClass = lpBaseClass;
m_fnCreator = fnCreator;
m_lpPrevClass = x_lpRuntimeClassListHead;
x_lpRuntimeClassListHead = this ;
}
SObject * gCreateObject(LPCTSTR szClassName)
{
const SRuntimeClass * pRC = x_lpRuntimeClassListHead;
for ( ; pRC; pRC = pRC -> m_lpPrevClass)
if ( ! _tcscmp(szClassName, pRC -> m_szClassName))
return pRC -> fnCreateObject();
return NULL;
}
BOOL SObject::IsKindOf( const SRuntimeClass * pBaseClass) const
{
SRuntimeClass * pRC = this -> GetRuntimeClass();
for ( ; pRC; pRC = pRC -> m_lpBaseClass)
if (pRC == pBaseClass)
return TRUE;
return FALSE;
}
- 序列化(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)。读盘时先读出类信息,由此重建对象实例。代码如下:
void SArchive::WriteObject(SObject * pOb)
{
WriteString(pOb -> GetRuntimeClass() -> m_szClassName);
pOb -> Write( * this );
}
对于情形a)的ReadObject:
HRESULT SArchive::ReadObject(SObject * pOb)
{
LPCTSTR lpsz = ReadString();
if ( ! _tcscmp(pOb -> GetRuntimeClass() -> m_szClassName, lpsz))
{
delete[] lpsz;
pOb -> Read( * this );
return S_OK;
}
delete[] lpsz;
return E_FAIL;
}
对于情形b)的ReadObject:
HRESULT SArchive::ReadObject(
const SRuntimeClass * pClassReq,
SObject ** ppOb)
{
LPCTSTR szReadedClass = 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 );
return S_OK;
}
return E_FAIL;
}
在此基础上,序列化中的另一个问题是,对象间的循环引用问题。在一个复杂的数据结构中,往往有A对象引用B对象,同时B对象又引用A对象。此时用上面的序列化机制,就会出现死循环。MFC以一种巧妙的方式解决了此问题。其核心思想是引入一个Hash表,将已经保存(或即将要保存)的对象指针与对象id(由系列化机制分配)联系起来。在第二次保存此对象时只是保存对象id,这样也就中断了循环。从这一个角度讲,序列化是相当优秀的。它可以轻易地保存任何一种复杂的数据结构。
考虑了循环引用问题后的对象读写函数如下:
void SArchive::WriteObject(SObject * pOb)
{
WriteString(pOb -> GetRuntimeClass() -> m_szClassName);
UINT nObIndex = - 1 ;
if (x_pMap -> Lookup(pOb, & nObIndex))
{
// 如果对象已经保存!
* this << nObIndex;
}
else
{
* this << (UINT) - 1 ;
// 规定-1表示对象是正常存盘
x_pMap -> SetAt(pOb, ::gGetObIndex(pOb));
// 这里gGetObIndex是系统为pOb对象分配id号的函数
// MFC中只是简单地用一个递增编号而已。
pOb -> Write( * this );
}
}
HRESULT SArchive::ReadObject(
const SRuntimeClass * pClassReq,
SObject ** ppOb)
{
* ppOb = NULL;
LPCTSTR szReadedClass = ReadString();
UINT nObIndex = - 1 ;
* this >> nObIndex;
if (nObIndex != (UINT) - 1 )
{
// 已经被保存过的情形!!!
if (pClassReq &&
_tcscmp(szReadedClass, pClassReq -> m_szClassName))
{
delete[] szReadedClass;
return E_FAIL;
}
delete[] szReadedClass;
* ppOb = ::gGetObByIndex(nObIndex);
return S_OK;
}
// 否则,正常情形,同以前一样!!!
if (pClassReq)
{
if ( ! _tcscmp(szReadedClass, pClassReq -> m_szClassName))
* ppOb = pClassReq -> m_fnCreator();
}
else
* ppOb = ::gCreateObject(szReadedClass);
delete[] szReadedClass;
if ( * ppOb)
{
( * ppOb) -> Read( * this );
return S_OK;
}
return E_FAIL;
}