TN002: Persistent Object Data Format(永久物件的資料格式) (轉)

worldblog發表於2007-12-14
TN002: Persistent Object Data Format(永久物件的資料格式) (轉)[@more@]

TN002: Persistent Data Format(永久的資料格式):namespace prefix = o ns = "urn:schemas--com::office" />

摘要:

這是篇筆記主要描述支援永久物件的MFC和當物件資料儲存成時的格式。

 

1、  MFC利用緊湊的二進位制格式對資料進行儲存,每個物件在檔案中只存在一個實體。物件自已的Serialize()才是提供真正的儲存。

2、  Carchive::WriteObject透過寫入資料頭來重建物件。資料頭包括物件型別和物件狀態兩個部分。此函式同樣也負責寫入一識別符號,以此來保持一個物件只有一份複製。

3、  讀取和寫入物件需要依依靠幾個預定義的常量,如下表:

Tag(識別符號)

Description(描述)

wNullTag

用於一個指向NULL的物件.

wNewClassTag

指出其後面是一個新的物件

wOldClassTag

指出將要讀取的是一個已讀取過的物件

wClassTag

類指示符

wBigObjectTag

0x8000000 指示一個大類標記

nMaxMapCount

0x3FFFFFFE 表示最大的mapCount值

4、  當儲存物件時Carchive維持了一個把要儲存的物件對映到一個32位識別符號PID的CmapPtrToPtr物件(m_pStoreMap)。PID的值是從1始的,PID只在它的作用域範圍內有效。

5、  儲存物件所用到的函式及相關程式碼:

void CArchive::WriteObject(const CObject* pOb)

{

  // 物件可以為 NULL

D nObIndex;

  // 初始化m_pStoreMap

  MapObject(NULL);

  if (pOb == NULL)

  {

  // 儲存NULL指標標誌

  *this << wNullTag;

  }

  else if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)

  // assumes initialized to 0 map

  {

  //儲存已儲存過物件的INDEX

  if (nObIndex < wBigObjectTag)

    *this << (WORD)nObIndex;

  else

  {

    *this << wBigObjectTag;

    *this << nObIndex;

  }

  }

  else

  {

  // 寫入示儲存過的物件

    CRuntimeClass* pClassRef = pOb->GetRuntimeClass();

    WriteClass(pClassRef);

  // enter in stored object table, checking for overflow

    CheckCount();

    (*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;

  // 使物件自身進行系列化

    ((CObject*)pOb)->Serialize(*this);

  }

}

 

void CArchive::WriteClass(const CRuntimeClass* pClassRef)

{

  // 確認pStoreMap已被初始化

  MapObject(NULL);//在這裡實際沒有做什麼事情

  // 寫入物件的ID和指示符並把指示符放在高位.

  // new object follows

  //假定map以初始化為0

  DWORD nClassIndex;

  if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)

  {

  // previously seen class, write out the index tagged by high bit

  if (nClassIndex < wBigObjectTag)

    *this << (WORD)(wClassTag | nClassIndex);

  else

  {

    *this << wBigObjectTag;

    *this << (dwBigClassTag | nClassIndex);

  }

  }

  else

  {

  // 儲存新物件

  *this << wNewClassTag;

  //儲存RUNTIME_CLASS物件所需的資訊(如:物件名字)

    pClassRef->Store(*this);

  // 儲存新物件的引用(參考)到MAP中

    CheckCount();

    (*m_pStoreMap)[(void*)pClassRef] = (void*)m_nMapCount++;

  }

}

 

6、  當ReadObject從檔案中讀取物件時透過判斷PID是否大於當前陣列的上限,如果大於則是一個新的物件。

7、  實現多版本化所要做的幾點和MFC內部實現的部份程式碼。

l  在IMPLEMENT_SERIAL中要使nSchemaVERSIONABLE_SCHEMA與當前版本號進行或運算所得的值。

l  在Serialize中透過GetObjectSchema來獲版本號,並進行相應處理。

具體程式碼如下:

#define VERSIONABLE_SCHEMA  (0x80000000)

IMPLEMENT_SERIAL(CMyObject, CObject, VERSIONABLE_SCHEMA|1)

void CMyObject::Serialize(CArchive& ar)

{

  if (ar.IsLoading())

  {

  int nVersion = ar.GetObjectSchema();

 

  switch(nVersion)

  {

  case 0:

  // read in previous version of

  // this object

  break;

  case 1:

  // read in current version of

  // this object

  break;

  default:

  // report unknown version of

  // this object

  break;

  }

  }

  else

  {

  // Normal storing code goes here

  }

}

8、  在一些情況下需要直接Serialize,這樣帶來了一些好處但也帶來的不好的地方。主要有四個方面:

l  可以使檔案體更小,而且可以支援更多的檔案格式。

l  ReadObject和WriteObject以及一些與它們有關的東西不會連線到你的程式中去,除非你要支援更多通用物件的存檔方案。

l  反系列化時需要自行處理更多的事情。

l  不能呼叫CArchive::GetObjectSchema或者要使CArchive::GetObjectSchema返回-1來表示未知版本。

由於直接呼叫Serialize會造成有時在子物件中要儲存父文件物件的指標,這需要兩步來實現:1、在子物件中必需顯式地儲存一個指向父文件的back pointer;2、在back pointer存檔之前,透過呼叫CArchive::MapObjectCdocument對映一個PID。

 

注:具體內部流程參考《深入淺出MFC》PART4—P60


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993350/,如需轉載,請註明出處,否則將追究法律責任。

相關文章