EternalWindows
OLE埋め込み / オブジェクトの保存

今回作成することになるコンテナアプリケーションは、一連のオブジェクトをファイルに保存する機能を備えています。 保存したファイルを開いた場合は一連のオブジェクトがロードされ、 各オブジェクトは保存時に表示されていた適切な位置に描画されることになります。 オブジェクトを保存するためには、その保存先となる構造化ストレージをオブジェクトに渡さなければならないため、 まずは構造化ストレージを作成することになります。

BOOL CContainer::CreateRootStorage(LPWSTR lpszFileName)
{
	CLSID clsid;
	WCHAR szStreamName[] = L"stream";

	if (lpszFileName == NULL) {
		StgCreateDocfile(NULL, STGM_READWRITE | STGM_TRANSACTED, 0, &m_pRootStorage);
		if (m_pRootStorage == NULL)
			return FALSE;

		WriteClassStg(m_pRootStorage, CLSID_SampleFile);
		
		m_pRootStorage->CreateStream(szStreamName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStream);
		if (m_pStream == NULL)
			return FALSE;
	}
	else {
		IStorage *pStorage;
		IStream  *pStream;

		StgOpenStorage(lpszFileName, NULL, STGM_READWRITE | STGM_TRANSACTED, NULL, 0, &pStorage);
		if (pStorage == NULL)
			return FALSE;

		ReadClassStg(pStorage, &clsid);
		if (!IsEqualCLSID(clsid, CLSID_SampleFile)) {
			pStorage->Release();
			return FALSE;
		}

		pStorage->OpenStream(szStreamName, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pStream);
		if (pStream == NULL) {
			pStorage->Release();
			return FALSE;
		}

		Destroy();
	
		m_pRootStorage = pStorage;
		m_pStream = pStream;
	}

	return TRUE;
}

構造化ストレージはファイルシステムのように階層を持つことが可能であり、 このメソッドでは最上位のルートストレージを作成します。 lpszFileNameがNULLの場合は、一時ファイルに支持される構造化ストレージをStgCreateDocfileで作成します。 STGM_TRANSACTEDを指定していることから、ストレージに対する書き込みはCommitを呼び出すまでファイルには反映されません。 また、STGM_SHARE_EXCLUSIVEを指定すると後述のIRootStorage::SwitchToFileの呼び出しで失敗するため、指定しないようにします。 WriteClassStgでルートストレージにCLSIDを書き込んでいるのは、作成したファイルを一意に識別できるようにするためです。 これは必須な処理ではありません。 CreateStreamでルートストレージの直下にストリームを作成しているのは、 一連のオブジェクトの数や名前といった補足情報を書き込むためです。 ストリームにはSTGM_SHARE_EXCLUSIVEが必須であり、STGM_TRANSACTEDは指定することができません。 オブジェクト自体はこのストリームではなく、別個サブストレージに格納されることになります。 lpszFileNameがNULLでない場合は、StgOpenStorageでストレージをオープンします。 オープンしたストレージがこのアプリケーションによって作成されたものかを識別するために、 ReadClassStgで取得したCLSIDとファイルのCLSIDを比較します。 新しくストレージをオープンしたことによって既に作成したデータが不要になるため、 Destroyという自作メソッドで破棄します。

オブジェクトを保存するためには、オブジェクトに対して構造化ストレージを渡さなければなりませんが、 これはルートストレージではなく、ルートストレージの直下に作成したサブストレージです。 サブストレージの作成は、前々節で取り上げたCreateObjectで行っています。

wsprintfW(szObjectName, L"オブジェクト %d", m_nObjectCount + 1);
m_pRootStorage->CreateStorage(szObjectName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStorage);

CreateStorageを呼び出せば、m_pRootStorageの下にサブストレージを作成することができます。 第1引数はサブストレージの名前であり、今回は"オブジェクト 1"のような形にしています。 取得したpStorageはCSite::Initによってメンバ変数に保存されることになります。

コンテナのメニューから「保存」を選択した場合は、SaveObjectが呼ばれます。 ここでは、現在作成されている一連のオブジェクトがファイルに保存されます。

BOOL CContainer::SaveObject()
{
	int           i;
	ULONG         uResult;
	RECT          rc;
	CSite         *p = m_pSite;
	WCHAR         szFilePath[MAX_PATH];
	LARGE_INTEGER li = {0};

	if (m_nObjectCount == 0) {
		MessageBox(NULL, TEXT("オブジェクトが作成されていません。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	if (!FileDialog(FALSE, szFilePath, MAX_PATH))
		return FALSE;

	m_pStream->Seek(li, STREAM_SEEK_SET, NULL);
	m_pStream->Write(&m_nObjectCount, sizeof(int), &uResult);

	for (i = 0; i < m_nObjectCount; i++) {
		p->GetRect(&rc);
		m_pStream->Write(&rc, sizeof(RECT), &uResult);

		p->Save();
		p = p->m_pNext;
	}

	if (lstrcmpW(szFilePath, m_szFilePath) != 0) {
		HRESULT      hr;
		IRootStorage *pRootStorage;

		m_pRootStorage->QueryInterface(IID_PPV_ARGS(&pRootStorage));
		hr = pRootStorage->SwitchToFile(szFilePath);
		pRootStorage->Release();
		if (FAILED(hr)) {
			m_pRootStorage->Revert();
			return FALSE;
		}

		lstrcpyW(m_szFilePath, szFilePath);
	}

	m_pRootStorage->Commit(STGC_DEFAULT);

	return TRUE;
}

m_nObjectCountが0である場合は保存するオブジェクトが存在しないため、処理を続行しないようにします。 FileDialogという自作メソッドにFALSEを指定した場合はファイルを保存するダイアログが表示され、 そこで選択したファイルが第2引数に返されます。 ストリームへの書き込みはWriteで可能ですが、 その前にSeekを呼び出すことで常にストリームの先頭から書き込まれるようにしておきます。 書き込まれる内容は先頭にオブジェクトの数であり、 その後に個々のオブジェクトの位置(RECT構造体)が書き込まれていきます。 p->Saveは内部でOleSaveを呼び出し、これによってオブジェクトがサブストレージにデータを保存します。 一通りの保存が完了すれば、書き込み内容がファイルに反映されるようにCommitを呼び出せばよいのですが、 このためにはルートストレージの出力先となるファイルパスがszFilePathのものでなければなりません。 よって、現在のパス(m_szFileName)がszFilePathと異なる場合は、 IRootStorage::SwitchToFileによって出力先を変更します。

コンテナのメニューから「開く」を選択した場合は、LoadObjectが呼ばれます。 ここでは、保存していたオブジェクトがロードされます。

BOOL CContainer::LoadObject()
{
	int        i;
	ULONG      uResult;
	RECT       rc;
	WCHAR      szObjectName[256];
	WCHAR      szFilePath[MAX_PATH];
	CSite      *p = NULL;
	IOleObject *pOleObject;
	IStorage   *pStorage;

	if (!FileDialog(TRUE, szFilePath, MAX_PATH))
		return FALSE;

	if (!CreateRootStorage(szFilePath))
		return FALSE;

	lstrcpyW(m_szFilePath, szFilePath);

	m_pStream->Read(&m_nObjectCount, sizeof(int), &uResult);
	for (i = 0; i < m_nObjectCount; i++) {
		wsprintf(szObjectName, L"オブジェクト %d", i + 1); 
		m_pRootStorage->OpenStorage(szObjectName, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, NULL, 0, &pStorage);
		OleLoad(pStorage, IID_IOleObject, NULL, (void **)&pOleObject);

		m_pStream->Read(&rc, sizeof(RECT), &uResult);

		if (i == 0) {
			m_pSite = new CSite;
			p = m_pSite;
		}
		else {
			p->m_pNext = new CSite;
			p = p->m_pNext;
		}

		p->Initialize(pOleObject, pStorage, szObjectName, &rc);
	}

	return TRUE;
}

ファイルを構造化ストレージとしてオープンするには、CreateRootStorageにファイルパスを指定します。 このファイルパスはFileDialogの第1引数にTRUEを指定することで、 ファイルを開くダイアログから取得できます。 CreateRootStorageが成功したらm_pStreamにオブジェクトの補足情報が格納されているため、 まずはReadでオブジェクトの数を取得します。 SaveObjectでは最初にオブジェクトの数を書き込んでいたため、これは成立します。 オブジェクトの数を取得したら、その数だけをオブジェクトをロードすることになります。 まず、オブジェクトの名前をインデックスベースで作成し、 これをOpenStorageに指定することで、オブジェクトが格納されているサブストレージをオープンします。 そしてこれをOleLoadに指定すれば、CLSIDの取得、CoCreateInstanceの呼び出し、IPersistStorage::Loadの呼び出しなどの一連の処理が行われ、 オブジェクトを識別するIOleObjectを取得できます。 これが終われば、このオブジェクトのための位置をストリームから取得し、 オブジェクトと通信するためのCSiteを作成します。 そしてInitを呼び出すことで、オブジェクトやサブストレージなどの各情報をメンバ変数に保存します。


戻る