共用方式為


TN002: 永續性物件的資料格式

這張便箋說明 MFC 常式支援永續性的 C++ 物件和物件資料的格式儲存檔案時。 這只適用於具有類別DECLARE_SERIALIMPLEMENT_SERIAL巨集。

問題

永續性資料的 MFC 實作儲存單一的連續檔案部份的許多物件的資料。 物件的Serialize方法會將物件的資料轉譯成壓縮的二進位格式。

實作可保證所有的資料會儲存在相同的格式,使用CArchive 類別。 它會使用CArchive的翻譯員的物件。 此物件仍然存在,則會建立它直到您呼叫的時間從CArchive::Close。 呼叫這個方法可以由程式設計人員明確或隱含的解構函式在程式結束所包含的範圍時CArchive

這張便箋說明的實作CArchive成員CArchive::ReadObjectCArchive::WriteObject。 您會發現程式碼為 Arcobj.cpp 和主要的實作中這些函式CArchive Arccore.cpp 中。 使用者程式碼不會呼叫ReadObjectWriteObject直接。 相反地,這些物件由類別的特定型別安全插入和擷取運算子,就會自動地會產生DECLARE_SERIALIMPLEMENT_SERIAL巨集。 下列程式碼說明如何WriteObjectReadObject隱含地呼叫:

class CMyObject : public CObject
{
    DECLARE_SERIAL(CMyObject)
};

IMPLEMENT_SERIAL(CMyObj, CObject, 1)

// example usage (ar is a CArchive&)
CMyObject* pObj;
CArchive& ar;
ar << pObj;        // calls ar.WriteObject(pObj)
ar >> pObj;        // calls ar.ReadObject(RUNTIME_CLASS(CObj))

將物件儲存至存放區 (CArchive::WriteObject)

此方法CArchive::WriteObject會將用來重建物件的標頭資料。 這個資料包含兩個部分: 物件類型和物件的狀態。 這個方法也是負責維護寫出,物件的識別,使儲存單一複本,而不考慮數目的指標,該物件 (包括循環的指標)。

儲存 (插入),以及還原 (解壓縮) 物件會依賴數個 「 資訊清單常數。 」這些是儲存在二進位檔,並提供重要資訊到保存檔 (請注意"w"前置詞會指示 16 位元的數目) 的值:

Tag

描述

wNullTag

用於 NULL 物件指標 (0)。

wNewClassTag

表示類別描述接下來是封存本文 (-1) 中的新功能。

wOldClassTag

指出正在讀取物件的類別中發生過此內容 (0x8000)。

封存儲存時的物件,可維護 CMapPtrToPtr ( m_pStoreMap) 也就是一個預存物件對應到 32 位元永續性識別碼 (PID)。 PID 係授與每個唯一的物件,並儲存在封存的內容中的每個唯一的類別名稱。 這些 Pid 所分發以循序方式從 1 開始。 這些 Pid 封存的範圍之外有沒有意義,而且,特別是,是不應該與記錄號碼或其他識別項目混淆。

CArchive類別,Pid 是 32 位元,但它們會寫出為 16 位元除非它們是大於 0x7FFE。 大型的 Pid 所寫成 32 位元 PID 所遵循的 0x7FFF。 這會維護相容性與較早版本中所建立的專案。

當儲存至封存檔的物件 (通常是使用通用插入運算子) 提出要求時,檢查撤銷狀態的 null 值 CObject 指標。 如果指標為 NULL, wNullTag會被插入到保存資料流。

如果指標不是 NULL,並可序列化 (此類別是DECLARE_SERIAL類別),程式碼檢查m_pStoreMap來查看物件是否被已經儲存。 如果是,程式碼中插入與該物件相關聯,插入保存資料流的 32 位元 PID。

如果之前尚未儲存的物件,都要考慮的兩種可能性: 物件和物件的精確型別 (也就是類別) 的新使用者,封存本文中,或是物件位於的精確型別已經看過。 若要判斷是否已看過的型別,撰寫查詢m_pStoreMapCRuntimeClass 符合物件CRuntimeClass所儲存的物件相關聯的物件。 如果沒有相符項目, WriteObject插入標記之間的OR的wOldClassTag與此索引。 如果CRuntimeClass的新功能來封存本文中, WriteObject將新的 PID 指派給該類別,並將它插入封存,前面加上wNewClassTag的值。

這個類別的描述元會插入至封存使用CRuntimeClass::Store方法。 CRuntimeClass::Store插入結構描述的數目 (如下所示) 的類別和 ASCII 文字類別的名稱。 請注意,ASCII 文字名稱的用法並不保證唯一性封存的所有應用程式。 因此,您應該標記您的資料檔案,以避免損毀。 類別資訊的插入動作,只要遵循封存會將物件插入m_pStoreMap ,接著再呼叫Serialize方法來將類別的特定資料。 將物件插入m_pStoreMap呼叫之前Serialize防止多個物件複本儲存至存放區。

在傳回給初始的呼叫端 (通常是根物件的網路) 時,您必須呼叫CArchive::Close。 如果您計畫執行其他 CFile作業,您必須呼叫CArchive方法排清以避免損毀的保存檔。

注意事項注意事項

這項實作會為每個封存內容的 0x3FFFFFFE 索引的嚴格限制。這個數字代表唯一的物件和類別可以儲存在單一的保存的最大數目,但單一磁碟檔可包含無數個封存內容。

從存放區 (CArchive::ReadObject) 載入物件

正在載入 (解壓縮) 物件使用CArchive::ReadObject方法是相反的情況,並WriteObject。 如同WriteObjectReadObject不直接由使用者程式碼 ; 呼叫 使用者程式碼應該呼叫型別安全引出運算子會呼叫ReadObject與預期的CRuntimeClass。 這樣可確保擷取作業的類型的完整性。

由於WriteObject實作指派日益增多的 Pid,從 1 開始 (0 已預先定義成 NULL 物件), ReadObject實作可以使用陣列來維護狀態的保存檔內容。 PID 讀取時從存放區中,如果 PID 值大於目前上限m_pLoadArrayReadObject知道新的物件 (或類別描述) 之後。

結構描述的數字

結構描述的數字,該值會指定給類別時IMPLEMENT_SERIAL類別的方法發生時,「 版本 」 的類別實作。 結構描述指的是類別的實作,不必次數指定的物件已經造成持續性 (通常是指物件版本)。

如果您想要維護相同的類別數種不同的實作方式,經過一段時間,遞增的結構描述當您修改物件的Serialize方法的實作可以讓您撰寫程式碼,可以載入儲存藉由實作的較舊版本的物件。

CArchive::ReadObject方法會擲回 CArchiveException 時遭遇不同於記憶體中之類別描述的結構描述編號永續性存放區中的結構描述數字。 不容易修復這個例外狀況。

您可以使用VERSIONABLE_SCHEMA加上 (位元OR) 您的結構描述版本保持擲回這個例外狀況。 藉由使用VERSIONABLE_SCHEMA,您的程式碼可以採取適當的動作,其Serialize函式,方式是檢查傳回值,從CArchive::GetObjectSchema

電話直接序列化

在許多情況下的 「 一般 」 物件保存配置的額外負荷WriteObjectReadObject並不需要。 這是最常見的情況,將資料 CDocument。 如此一來, Serialize方法的CDocument直接被呼叫,不使用 [擷取] 或 [插入運算子。 文件的內容可能會依序使用較為普遍的物件保存配置。

呼叫Serialize直接有下列優缺點:

  • 沒有額外的位元組會加入到保存檔之前或之後將物件序列化。 這不只讓已儲存的資料比較小,但可讓您實作Serialize常式可處理任何檔案格式。

  • 已調整 MFC 所以WriteObjectReadObject實作和相關的集合不會連結到您的應用程式除非必要,較為普遍的物件保存配置因某些其他原因。

  • 您的程式碼並沒有從舊的結構描述數字中復原。 這可讓您的文件序列化程式碼負責編碼結構描述數字、 檔案格式版本號碼,或任何用來識別數字開頭的資料檔案使用。

  • 會以直接呼叫序列化任何物件Serialize絕對不能使用CArchive::GetObjectSchema或,必須控制代碼傳回值為-1 (UINT) 指出版本不明。

因為Serialize稱為直接在文件時,就無法通常為封存其父代文件的參考文件的子物件。 這些物件必須指標至其容器文件明確給予或您必須使用CArchive::MapObject函式,來對應CDocument為 PID,才能保存這些反指標的指標。

如先前所述,您應該進行編碼的版本且類別資訊自己當您呼叫Serialize直接,讓您稍後變更格式,同時保有回溯相容性較舊的檔案。 CArchive::SerializeClass可以呼叫函式明確地直接序列化物件前日期或之前呼叫基底類別。

請參閱

其他資源

技術的備忘稿編號

依類別的技術注意事項