オブジェクトを保存したいコンテナは、保存先となる構造化ストレージをオブジェクトに渡すことで、 オブジェクト自身にデータを保存させる機会を与えます。 オブジェクト自身が保存を行うことで、コンテナがオブジェクトのフォーマットを理解する必要がなくなるため、 このような設計になっているのではないかと思われます。 オブジェクトがIPersistStorageを実装していれば、コンテナはそれを通じて保存などの命令を送ることができます。 以下、IPersistStorageのメソッドを順に見ていきます。
STDMETHODIMP CObject::GetClassID(CLSID *pClassID) { *pClassID = CLSID_SampleObject; return S_OK; }
GetClassIDは、オブジェクトのCLSIDを返すようにします。 このメソッドは正確にはIPersistのメソッドですが、 IPersistStorageはIPersistを継承しているので実装する必要があります。
STDMETHODIMP CObject::IsDirty() { if (m_bDirty) return S_OK; else return S_FALSE; }
IsDirtyは、オブジェクトがダーティ状態であるかを返すようにします。 オブジェクトに対して何らかの変更(今回の場合は図形の描画)が行われた場合はダーティ状態になり、 オブジェクトを保存した場合はダーティ状態ではなくなります。
STDMETHODIMP CObject::InitNew(IStorage *pStg) { if (m_pStream != NULL) return E_UNEXPECTED; pStg->CreateStream(m_szStreamName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStream); m_pStorage = pStg; m_pStorage->AddRef(); return S_OK; }
InitNewは、オブジェクトに初期化の機会を与えるためにOleUIInsertObjectの内部で呼ばれます。 ここでは、引数として渡された構造化ストレージの中にデータ格納用のストリームを作成します。 このような作業は保存時(Save)でも行うことができそうですが、 保存時にはメモリの確保を行うべきではないとされているため、ここで行うようにします。 構造化ストレージとストリームは他のメソッドで必要になるため、メンバとして保存することになります。
STDMETHODIMP CObject::Save(IStorage *pStgSave, BOOL fSameAsLoad) { int i; ULONG uResult; IStream *pStream; LARGE_INTEGER li = {0}; m_pStream->Seek(li, STREAM_SEEK_SET, NULL); m_pStream->Write(&m_nShapeCount, sizeof(int), &uResult); for (i = 0; i < m_nShapeCount; i++) { m_pStream->Write(&m_shape[i].rc, sizeof(RECT), &uResult); m_pStream->Write(&m_shape[i].bRectangle, sizeof(BOOL), &uResult); } m_bDirty = FALSE; return S_OK; }
Saveは、コンテナのメニューから「保存」が選択された場合や、 オブジェクトのウインドウを閉じた場合に呼ばれます。 引数として渡された構造化ストレージにオブジェクトのデータを保存することになります。 ストリームに格納するデータのフォーマットは最初に図形の総数となり、 その後に図形の数だけ位置と長方形であるかどうかのフラグが続きます。 なお、上記では常に全ての図形を一から書き込んでいるわけですが、 これは本来ならfSameAsLoadがFALSEの場合の処理です。 もし、fSameAsLoadがTRUEならば差分情報のみを書き込むようにしなければならないのですが、 今回はこの動作を割愛しています。
STDMETHODIMP CObject::SaveCompleted(IStorage *pStgNew) { if (pStgNew != NULL) { HandsOffStorage(); pStgNew->OpenStream(m_szStreamName, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &m_pStream); if (m_pStream == NULL) return E_FAIL; m_pStorage = pStgNew; m_pStorage->AddRef(); } return S_OK; }
SaveCompletedは、コンテナが保存処理を終了した際に呼ばれますが、 意味的にはストリームへの書き込みを再開する目的を含んでいます。 実はSaveが制御を返した後、現在維持しているストリームにはデータを書き込むべきではないとされており、 それはSaveCompletedまで続くべきとされています。 しかし実際のところ、Saveの後にはSaveCompleted(NULL)が直ちに呼ばれると思われるため、 特に意識することはないでしょう。 pStgNewがNULLでない場合は、これからはこのストレージを使用しなければならないため、 HandsOffStorageで現在のメモリを開放してから、データ格納用のストリームをオープンします。
STDMETHODIMP CObject::HandsOffStorage() { if (m_pStorage != NULL) { m_pStorage->Release(); m_pStorage = NULL; } if (m_pStream != NULL) { m_pStream->Release(); m_pStream = NULL; } return S_OK; }
HandsOffStorageは、現在維持しているストレージとストリームのポインタを開放します。 基本的にコンテナから呼ばれることはないと思われますが、オブジェクト内ではデストラクタやSaveCompletedから呼ばれています。
STDMETHODIMP CObject::Load(IStorage *pStg) { int i; ULONG uResult; IStream *pStream; if (SaveCompleted(pStg) != S_OK) return E_FAIL; m_pStream->Read(&m_nShapeCount, sizeof(int), &uResult); for (i = 0; i < m_nShapeCount; i++) { m_pStream->Read(&m_shape[i].rc, sizeof(RECT), &uResult); m_pStream->Read(&m_shape[i].bRectangle, sizeof(BOOL), &uResult); } m_bDirty = FALSE; return S_OK; }
Loadは、コンテナのメニューから「開く」が選択された場合や、 ユーザーが休止状態のオブジェクトをダブルクリックした場合に呼ばれます。 データを取得するにはストリームが必要になりますから、 SaveCompletedにpStgを指定することで、この構造化ストレージのストリームを内部でオープンします。 これが成功すればストリームのフォーマットに従い、まずは図形の数を取得します。 そして、図形の数だけ位置とフラグを取得することになります。