In Chapter 5's version of Polyline, the IPolyline5 interface had two member functions, ReadFromFile and WriteToFile. Because CoCosmo now uses compound files, these IPolyline5 members are no longer useful. (This sample doesn't retain backward compatibility with the traditional file–based version of Chapter 5.) In their place, Polyline uses implementations of both the IPersistStorage and IPersistStreamInit interfaces.6 This code is found in CHAP08\POLYLINE. To support and manage the additional interfaces, Polyline requires a few minor modifications to its QueryInterface and its initialization and cleanup code. The primary changes are the interface implementations themselves, in IPERSTOR.CPP and IPERSTMI.CPP. Note also that Polyline now uses IPolyline8 and IPolylineAdviseSink8 (instead of the Chapter 5 versions), which are defined in INC\IPOLY8.H.
Now, according to CoCosmo's initialization code, which we saw in the last section, CoCosmo will ask Polyline for IPersistStorage first and then use that interface exclusively if it's found. Because Polyline implements that interface and IPersistStreamInit (thus IPersistStream also), CoCosmo will never touch the stream-based implementation if you run the code as is. To test the stream interaction, either modify CoCosmo to not ask for IPersistStorage or modify Polyline to not respond to IID_IPersistStorage in its QueryInterface. Either change will cause CoCosmo to use IPersistStreamInit. You can make another modification to turn off this interface and have CoCosmo use IPersistStream. To avoid recompilation, run CoCosmo in a debugger and skip over the QueryInterface calls in CCosmoDoc::Init for the interfaces you want to omit.
The IPersistStream implementation is simple, as you can see in Listing 8-1. The GetClassID member returns a CLSID, IsDirty returns the right HRESULT depending on Polyline's dirty flag, GetSizeMax returns the size of Polyline's data structure, and InitNew doesn't do anything at all. (Polyline has no other state to initialize.) The bulk of the work happens in Load and Save, which do little more than call IStream::Read and IStream::Write.
/*
* IPERSTMI.CPP
* Polyline Component Chapter 8
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*/
#include "polyline.h"
[Constructor, Destructor, IUnknown members omitted]
STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
{
*pClsID=m_pObj->m_clsID;
return NOERROR;
}
STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
{
return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
}
STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
{
POLYLINEDATA pl;
ULONG cb;
HRESULT hr;
if (NULL==pIStream)
return ResultFromScode(E_POINTER);
//Read all data into the POLYLINEDATA structure.
hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb); if (FAILED(hr) || CBPOLYLINEDATA!=cb)
{
pIStream->Release();
return hr;
}
m_pObj->m_pImpIPolyline->DataSet(&pl, TRUE, TRUE);
return NOERROR;
}
STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
, BOOL fClearDirty)
{
POLYLINEDATA pl;
ULONG cb;
HRESULT hr;
if (NULL==pIStream)
return ResultFromScode(E_POINTER);
m_pObj->m_pImpIPolyline->DataGet(&pl);
hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
pIStream->Release();
if (FAILED(hr) || CBPOLYLINEDATA!=cb)
return ResultFromScode(STG_E_WRITEFAULT);
m_pObj->m_fDirty=fClearDirty;
return NOERROR;
}
STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
*pcbSize)
{
if (NULL==pcbSize)
return ResultFromScode(E_POINTER);
ULISet32(*pcbSize, CBPOLYLINEDATA);
return NOERROR;
}
STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
{
return NOERROR;
}
Listing 8-1
The IPersistStreamInit interface implementation for the Polyline object.
You can see why IPersistStream[Init] is the simplest persistence model. The implementation of IPersistStorage, which is shown in Listing 8-2, is more complicated. This code is a basic framework for any IPersistStorage implementation, in which values starting with "PSSTATE" identify the various object states such as scribble and zombie. The values are defined as follows in an enumerator in the common header file INC\INOLE.H because many other samples in the rest of this book will include similar implementations of IPersistStorage:
typedef enum
{
PSSTATE_UNINIT, //Uninitialized
PSSTATE_SCRIBBLE, //Scribble
PSSTATE_ZOMBIE, //No-scribble
PSSTATE_HANDSOFF //Hands-off
} PSSTATE;
Overall, the IPersistStorage implementation in Polyline demonstrates the correct changes in the object's storage state when it shifts among all the states. The only differences between this code and code for a more complex storage scheme are in the number of open elements managed by the object and in the complexity of the data stored in those elements. Thus, this implementation works as a solid framework for your own development needs.
/*
* IPERSTOR.CPP
* Polyline Component Chapter 8
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*/
#include "polyline.h"
CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
, LPUNKNOWN pUnkOuter)
{
[Other initialization omitted]
m_psState=PSSTATE_UNINIT;
return;
} [Destructor and IUnknown members omitted]
STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
{
if (PSSTATE_UNINIT==m_psState)
return ResultFromScode(E_UNEXPECTED);
*pClsID=m_pObj->m_clsID;
return NOERROR;
}
STDMETHODIMP CImpIPersistStorage::IsDirty(void)
{
if (PSSTATE_UNINIT==m_psState)
return ResultFromScode(E_UNEXPECTED);
return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
}
STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
{
HRESULT hr;
ULARGE_INTEGER uli;
if (PSSTATE_UNINIT!=m_psState)
return ResultFromScode(E_UNEXPECTED);
if (NULL==pIStorage)
return ResultFromScode(E_POINTER);
hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
| STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
, 0, 0, &m_pObj->m_pIStream);
if (FAILED(hr))
return hr;
//Preallocate stream space.
ULISet32(uli, CBPOLYLINEDATA);
m_pObj->m_pIStream->SetSize(uli); //We expect that client has called WriteClassStg.
WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);
m_pObj->m_pIStorage=pIStorage;
pIStorage->AddRef();
m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}
STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
{
POLYLINEDATA pl;
ULONG cb;
LPSTREAM pIStream;
HRESULT hr;
if (PSSTATE_UNINIT!=m_psState)
return ResultFromScode(E_UNEXPECTED);
if (NULL==pIStorage)
return ResultFromScode(E_POINTER);
//We don't check CLSID to remain compatible with other chapters.
hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);
if (FAILED(hr))
return ResultFromScode(STG_E_READFAULT);
//Read all data into the POLYLINEDATA structure.
hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);
if (FAILED(hr) || CBPOLYLINEDATA!=cb)
{
pIStream->Release();
return hr;
} m_pObj->m_pIStream=pIStream;
m_pObj->m_pIStorage=pIStorage;
pIStorage->AddRef();
m_pObj->m_pImpIPolyline->DataSet(&pl, TRUE, TRUE);
m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}
STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
, BOOL fSameAsLoad)
{
POLYLINEDATA pl;
ULONG cb;
LPSTREAM pIStream;
HRESULT hr;
//Have to come here from scribble state.
if (PSSTATE_SCRIBBLE!=m_psState)
return ResultFromScode(E_UNEXPECTED);
//Must have an IStorage if we're not in SameAsLoad.
if (NULL==pIStorage && !fSameAsLoad)
return ResultFromScode(E_POINTER);
if (fSameAsLoad)
{
LARGE_INTEGER li;
pIStream=m_pObj->m_pIStream;
LISet32(li, 0);
pIStream->Seek(li, STREAM_SEEK_SET, NULL);
//This matches the Release below.
pIStream->AddRef();
}
else
{
hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
| STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
, 0, 0, &pIStream);
if (FAILED(hr))
return hr; //Do this only with new storages.
WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);
}
//DataGet does not make allocations; it's just a memory copy.
m_pObj->m_pImpIPolyline->DataGet(&pl);
hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
pIStream->Release();
if (FAILED(hr) || CBPOLYLINEDATA!=cb)
return ResultFromScode(STG_E_WRITEFAULT);
m_psState=PSSTATE_ZOMBIE;
return NOERROR;
}
STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE
pIStorage)
{
HRESULT hr;
LPSTREAM pIStream;
//Must be called in no-scribble or hands-off state.
if (!(PSSTATE_ZOMBIE==m_psState
|| PSSTATE_HANDSOFF==m_psState))
return ResultFromScode(E_UNEXPECTED);
//If we're in hands-off state, we'd better get storage.
if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
return ResultFromScode(E_UNEXPECTED);
if (NULL!=pIStorage)
{
hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
, &pIStream);
if (FAILED(hr))
return hr; if (NULL!=m_pObj->m_pIStream)
m_pObj->m_pIStream->Release();
m_pObj->m_pIStream=pIStream;
if (NULL!=m_pObj->m_pIStorage)
m_pObj->m_pIStorage->Release();
m_pObj->m_pIStorage=pIStorage;
m_pObj->m_pIStorage->AddRef();
}
m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}
STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
{
/*
* Must be in scribble or no-scribble state. A repeated call
* to HandsOffStorage is an unexpected error (bug in client).
*/
if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
return ResultFromScode(E_UNEXPECTED);
//Release held pointers.
if (NULL!=m_pObj->m_pIStream)
ReleaseInterface(m_pObj->m_pIStream);
if (NULL!=m_pObj->m_pIStorage)
ReleaseInterface(m_pObj->m_pIStorage);
m_psState=PSSTATE_HANDSOFF;
return NOERROR;
}
Listing 8-2
The IPersistStorage interface implementation for the Polyline object.
You can see that most of the implementation is fairly simple. GetClassID and IsDirty are trivial, and InitNew, SaveCompleted, and HandsOffStorage need only to manage IStorage and IStream pointers for use in Save. These three functions demonstrate how you should maintain these pointers so that you do not need to create any new objects in Save under a low-memory scenario. InitNew and Load also demonstrate preallocation of the object's needed stream space to avoid problems with a full storage medium later on.
Polyline's IPersistStorage::Load function simply opens the "CONTENTS" stream (the symbol SZSTREAM is defined as this string), reads the data, makes the data current, and holds on to the IStream pointer for Save. Because IStorage::OpenStream returns an IStream pointer with a reference count, we do not need an extra call to AddRef when we store that pointer in m_pIStream.
The implementation of IPersistStorage::Save is the interesting part. If fSameAsLoad is TRUE, we're writing into the storage we received during InitNew or Load, so we already have the IStream pointer in which to write our current data. If you look carefully at the implementation of Save in Listing 8-2, you will notice that it allocates no memory (and creates no new objects), fulfilling the requirement that Save will not fail because of an out-of-memory condition. When fSameAsLoad is FALSE, however, we have to write data into the IStorage passed to Save, so we have to create new streams. Again, the object is allowed to fail in this case if no memory is left. If the new storage object that we're writing into is to become the current one, we'll see it again in SaveCompleted.
Inside SaveCompleted, you can see that what we do depends on whether pIStorage is NULL or not. If it is NULL, we can just switch back to the scribble state, using all the same pointers we currently hold. Otherwise, we have to release those pointers and replace them with ones we open in the new storage, making this new storage current. Also, the implementation of HandsOffStorage does what it must, of course, by releasing the IStorage and IStream pointers to our open elements.
Finally, the storage created through these implementations of CoCosmo and Polyline is identical to those generated by Chapter 7's Cosmo, with the exception of the exact CLSID and format tags. A file from either sample can be opened in the other.
6 IPersistFile was not used for two reasons. First CoCosmo manages the document and has an IStorage pointer for it (or an IStream) and we want Polyline's data to be part of that document not in a separate file. Second Polyline is a good code base for a compound document content object (as we'll make it in Chapter 18) as well as an OLE control (Chapter 24). Implementing these interfaces now saves us work later. |