テクニカル ノート 2: 永続オブジェクトのデータ形式
このファイルを保存すると、永続的な C++ オブジェクトと、オブジェクトのデータの形式をサポートする MFC ルーチンについて説明します。これは、クラスを使用するのみに適用されます、 DECLARE_SERIALとIMPLEMENT_SERIALマクロ。
問題
MFC では、永続的なデータ ファイルの 1 つの連続した部分で多くのオブジェクトのデータを格納します。オブジェクトのSerializeメソッドは、オブジェクトのデータはコンパクト バイナリ形式に変換します。
すべてのデータが保存されているのと同じ形式でを使用して、実装を保証する、 CArchive クラス。それを使用して、 CArchiveオブジェクトは、変換します。このオブジェクトを作成が呼び出されるまでの時間からが続くCArchive::Close。範囲を含む、プログラムを終了する場合このメソッドは、プログラマが明示的にまたはデストラクターによって暗黙的に呼び出すことができます、 CArchive。
ここでの実装について説明、 CArchiveメンバー CArchive::ReadObjectとCArchive::WriteObject。Arcobj.cpp、およびメインの実装をこれらの関数のコードが検索されますCArchive Arccore.cpp で。ユーザー コードの呼び出しはないReadObjectとWriteObject直接。によって自動的に生成されるクラスのタイプ セーフな挿入および抽出演算子でこれらのオブジェクトを使用する代わりに、 DECLARE_SERIALとIMPLEMENT_SERIALマクロ。次のコードではWriteObjectとReadObject暗黙的に呼び出されます。
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 、オブジェクトを構築するために使用されるヘッダーのデータを書き込みます。このデータには 2 つの部分から構成されています。 オブジェクトと、オブジェクトの状態の種類。この方法はまた (循環ポインターなど) へのポインターの数に関係なく、1 つのコピーのみを保存するように、書き込まれるオブジェクトの id を維持するための責任です。
(挿入) を保存し、(抽出) のオブジェクトを復元するに依存していくつか「記号定数」。これらはバイナリで格納される、アーカイブ ("w"のプレフィックスが 16 ビット量を示す注) への重要な情報を提供する値です。
Tag |
Description |
---|---|
wNullTag |
NULL オブジェクト ポインター (0) を使用します。 |
wNewClassTag |
このアーカイブ コンテキスト (-1) に新規に依存するクラスの説明を示します。 |
wOldClassTag |
このコンテキスト (0x8000) で参照されているオブジェクトのクラスを受けたことを示します。 |
オブジェクトを保存するときは、アーカイブを維持、 CMapPtrToPtr (、 m_pStoreMap) が格納されたオブジェクトから、32 ビットの永続的な識別子 (PID) へのマッピング。一意のすべてのオブジェクトとアーカイブのコンテキストに保存されているすべての一意のクラス名は、PID が割り当てられます。これらの Pid は 1 から順渡されます。これらの Pid は、アーカイブの範囲外は意味を持たず、特に、レコードの番号またはその他の識別情報の項目と混同しないようにします。
CArchiveクラス、Pid は 32 ビットですが、0x7FFE よりも大きいのでない限り、16 ビットとして書き出されます。大規模な Pid 0x7FFF を 32 ビット PID の後に書き込まれます。これにより、以前のバージョンで作成されたプロジェクトとの互換性が維持されます。
(通常、グローバル演算子を使用して) をアーカイブするには、オブジェクトを保存する要求が行われると、特殊な検査を行う CObject ポインター。ポインターが NULL の場合、 wNullTagがアーカイブ ストリームに挿入されます。
ポインターが NULL ではないし、シリアル化できるかどうか (クラスは、 DECLARE_SERIALクラス)、コードのチェック、 m_pStoreMapオブジェクトが既に保存されているかどうかを確認します。ある場合、コードはアーカイブ ストリームにそのオブジェクトを関連付けられている 32 ビット PID を挿入します。
前にオブジェクトが保存されていない場合は、考慮すべき 2 つの方法は: オブジェクトと、オブジェクトの実際の型 (つまり、クラス) の両方がこのアーカイブ コンテキストに新しいですかはすでに固定型のオブジェクトをあります。種類が確認されているかどうかを判断するコードのクエリ、 m_pStoreMapの CRuntimeClass と一致するオブジェクト、 CRuntimeClassが保存されているオブジェクトに関連付けられたオブジェクト。ある場合、一致は、 WriteObject and は、タグを挿入ORのwOldClassTagとインデックス。場合は、 CRuntimeClassこのアーカイブ コンテキストには、新しいWriteObjectそのクラスには、新しい PID を割り当て、前にアーカイブを挿入、 wNewClassTag値。
このクラスの記述子は、その後を使用して、アーカイブに挿入されます、 CRuntimeClass::Storeメソッド。CRuntimeClass::Storeスキーマの数 (下記参照)、クラスと、クラスの名前に ASCII テキストを挿入します。ASCII のテキスト名の使用をアーカイブの一意性をアプリケーション間で保証しないことに注意してください。したがって、破損を防ぐために、データ ファイルにタグを付ける必要があります。クラスの情報の挿入後、アーカイブ オブジェクトに格納する、 m_pStoreMapし、呼び出して、 Serializeクラスに固有のデータを挿入するメソッド。オブジェクトを配置する、 m_pStoreMap呼び出しの前にSerializeをストアに保存されてから、オブジェクトの複数のコピーを防ぐことができます。
最初の呼び出し元に (通常はネットワーク オブジェクトのルート) を返す場合、かを呼び出す必要がありますCArchive::Close。その他を実行する場合は、 CFile操作をする必要があります呼び出す、 CArchiveメソッドフラッシュアーカイブの破損を防ぐために。
[!メモ]
この実装では、0x3FFFFFFE インデックス アーカイブ コンテキストあたりのハードに制限します。この番号の一意のオブジェクトとクラスは、1 つのアーカイブに保存できる最大数を表しますが、無制限の数の上限は、1 つのディスク ファイルことができます。
読み込みオブジェクト ストア (CArchive::ReadObject)
(抽出) を読み込んでオブジェクトを使用して、 CArchive::ReadObjectメソッドとは逆のWriteObject。同様にWriteObject、 ReadObject ; ユーザー コードで直接呼び出されません ユーザー コードを呼び出して、タイプ セーフな抽出演算子を呼び出す必要がありますReadObjectで、予想されるCRuntimeClass。これは、抽出処理のタイプの整合性が保証されます。
WriteObjectの実装に割り当てられて増加の Pid は 1 から始まる (0 は、NULL オブジェクトとして定義)、 ReadObject実装できますアーカイブ コンテキストの状態を維持するために配列を使用して。PID が読み取るとき、ストアからは、PID が現在上限よりも大きい場合は、 m_pLoadArray、 ReadObject 、新しいオブジェクト (クラスの説明) に依存することを知っています。
スキーマ番号
スキーマ番号は、クラスに割り当てられていると、 IMPLEMENT_SERIALクラスのメソッドが検出される、「バージョン」のクラスの実装です。クラスの実装に、スキーマを参照しますが、数が倍に、特定のオブジェクト (通常、オブジェクトのバージョンとして呼ばれます) 固定しました。
時間の経過とともに、同じクラスの複数の実装を維持する場合は、オブジェクトの内容を変更すると、スキーマの増加Serializeメソッドの実装は、実装の古いバージョンを使用して格納されているオブジェクトを読み込むことができるコードを記述する有効にします。
CArchive::ReadObjectメソッドを投げます、 CArchiveException スキーマはスキーマの数、メモリ内のクラスの説明と異なって、永続的なストアで検出したとき。この例外から回復するは簡単です。
使用することができますVERSIONABLE_SCHEMAと組み合わせて (ビットごとの or OR) この例外をスローするように、スキーマのバージョン。使用してVERSIONABLE_SCHEMA、コードを適切なアクションを実行できますは、 Serializeからの戻り値をチェックして関数CArchive::GetObjectSchema。
通話を直接シリアル化します。
多くのオーバーヘッドを一般的なオブジェクト アーカイブ方式の場合WriteObjectとReadObject必要はありません。これは、データをシリアル化するが一般的なケースでは、 CDocument。ここでは、 SerializeメソッドのCDocument抽出や挿入演算子されませんで直接呼び出されます。ドキュメントの内容より一般的なオブジェクト アーカイブ方式を使用できます。
呼び出すSerialize直接、次の長所と短所を持ちます。
前にアーカイブするか、オブジェクトをシリアル化すると余分なバイトが追加されますありません。これだけでなく、保存されているデータが小さくになりますが、実装することができますSerializeファイル形式のいずれかを処理するルーチン。
MFC が調整されているので、 WriteObjectとReadObjectリンクの実装し、関連するコレクションいないさせるアプリケーションにいくつか他の目的のためより一般的なオブジェクト アーカイブ方式必要がない場合。
コードが以前のスキーマ番号からの回復はありません。これは、ドキュメントのシリアル化コード エンコード スキーマ番号、ファイル形式のバージョン番号、またはあらゆる識別番号の責任になりますが、データ ファイルの先頭に使用します。
直接呼び出しがシリアル化されたオブジェクトSerializeを使用する必要がありますCArchive::GetObjectSchemaまたはする必要がありますを示すハンドルが戻り値が-1 (UINT) のバージョンが不明です。
Serialize呼び出されます文書に直接、通常は、親ドキュメントへの参照をアーカイブするのには、文書のサブオブジェクトをことはできません。これらのオブジェクトのポインターのコンテナー ドキュメントに明示的に与える必要がありますまたは使用する必要がありますCArchive::MapObject関数をマップするのには、 CDocumentこのバック ポインターが整理される前に、PID へのポインター。
前述したように、バージョンをエンコードおよびお問い合わせの際に情報をクラスする必要がありますSerialize直接、まだ古いファイルとの下位互換性を維持しながら、後の書式を変更するようになります。CArchive::SerializeClass関数は、オブジェクトを直接シリアル化する前に、または基本クラスを呼び出す前に明示的に呼び出されますことができます。