Of Clients and Persistent Objects: CoCosmo

Our earlier discussion of IPersistStorage outlined some of a client's responsibilities when dealing with an object using this persistence model. To demonstrate this, we'll use the Component Cosmo sample for this chapter (CHAP08\COCOSMO) as the client and a modification of Polyline (CHAP08\POLYLINE) as the object.

CoCosmo now uses compound files as its native file format, just as the Cosmo sample from Chapter 7 does. In order to save or load Polyline's data, CoCosmo queries Polyline for IPersistStorage. Failing that, it will query for IPersistStreamInit and then IPersistStream. Whichever interface it finds, CoCosmo follows the appropriate protocol, as the fragments of code shown in this section (taken from DOCUMENT.CPP) will illustrate.

To facilitate the handling of multiple persistence models, I've defined the following flags and data structure in the INC\INOLE.H file to hold one of the IPersist* pointers in a union:


typedef enum
{
PERSIST_UNKNOWN=0,
PERSIST_STORAGE,
PERSIST_STREAM,
PERSIST_STREAMINIT,
PERSIST_FILE
} PERSIST_MODEL;

typedef struct
{
PERSIST_MODEL psModel;
union
{
IPersistStorage *pIPersistStorage;
IPersistStream *pIPersistStream;
IPersistStreamInit *pIPersistStreamInit;
IPersistFile *pIPersistFile;
} pIP;
} PERSISTPOINTER;

This work is merely an implementation convenience for CoCosmo and later samples. CoCosmo includes a PERSISTPOINTER in its CCosmoDoc class named m_pp. This holds whatever pointer it obtained from the Polyline object along with the specific type of pointer it happens to be. The m_pp variable is set inside CCosmoDoc::Init through a series of QueryInterface calls:


BOOL CCosmoDoc::Init(...)
{
§

hr=CoCreateInstance(CLSID_Polyline8, NULL, CLSCTX_INPROC_SERVER
, IID_IPolyline8, (PPVOID)&m_pPL);

if (FAILED(hr))
[Show error and return.]

[Other initialization omitted]

hr=m_pPL->QueryInterface(IID_IPersistStorage
, (PPVOID)&m_pp.pIP.pIPersistStorage);

if (SUCCEEDED(hr))
m_pp.psModel=PERSIST_STORAGE;
else
{
hr=m_pPL->QueryInterface(IID_IPersistStreamInit
, (PPVOID)&m_pp.pIP.pIPersistStreamInit);

if (SUCCEEDED(hr))
m_pp.psModel=PERSIST_STREAMINIT;
else
{
hr=m_pPL->QueryInterface(IID_IPersistStream
, (PPVOID)&m_pp.pIP.pIPersistStream);

if (SUCCEEDED(hr))
m_pp.psModel=PERSIST_STREAM;
else
return FALSE;
}
}

return TRUE;
}

Whatever pointer we obtain here is released in CCosmoDoc::~CCosmoDoc along with the member m_pIStorage, which refers to the document's open compound file.

When loading an existing file or creating a new file in CCosmoDoc::Load, CoCosmo follows its responsibilities and passes an IStorage pointer to Polyline's IPersistStorage::Load or IPersistStorage::InitNew, or it passes an IStream pointer to IPersistStream::Load or calls IPersistStreamInit::InitNew. When CoCosmo has to call IPersistStream::Load, the code it uses to open the stream is exactly what would appear in an object's IPersistStorage::Load function, as we'll see later in our discussion of Polyline.

In all cases, CoCosmo holds onto the open IStorage pointer itself, regardless of what Polyline does with it. CoCosmo is the owner of that storage, but Polyline needs it in order to create any of its own streams or substorages. As the following code shows, CoCosmo saves the pointer after calling StgCreateDocfile (for a new file) or StgOpenStorage (for an existing file):


//From CCosmoDoc::Load

//For new files
hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
| STGM_CREATE | STGM_DELETEONRELEASE
| STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

if (FAILED(hr))
return DOCERR_COULDNOTOPEN;

if (PERSIST_STORAGE==m_pp.psModel)
m_pp.pIP.pIPersistStorage->InitNew(pIStorage);
else
{
if (PERSIST_STREAMINIT==m_pp.psModel)
m_pp.pIP.pIPersistStreamInit->InitNew();
}

m_pIStorage=pIStorage;

§

//For existing files
hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
| STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

if (FAILED(hr))
return DOCERR_COULDNOTOPEN;

if (PERSIST_STORAGE==m_pp.psModel)
hr=m_pp.pIP.pIPersistStorage->Load(pIStorage);
else
{
LPSTREAM pIStream;

hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

if (SUCCEEDED(hr))
{
//This also works for PERSIST_STREAMINIT
hr=m_pp.pIP.pIPersistStream->Load(pIStream);
pIStream->Release();
}

m_pIStorage=pIStorage;

Within CCosmoDoc::Save, CoCosmo calls IPersistStorage::Save or IPersistStream[Init]::Save, passing the appropriate pointer. Again, the extra step of opening a stream in which to save through IPersistStream is what the object would do inside IPersistStorage itself. The difference between the interfaces is mostly where that stream creation occurs. Anyway, in the case of IPersistStorage, CoCosmo passes either the existing IStorage pointer obtained when loading the document (for File Save) or a new IStorage pointer from StgCreateDocfile when writing a new file (File Save As). It sets fSameAsLoad to TRUE in the former case, FALSE in the latter. After calling IPersistStorage::Save, we have to complete the protocol by calling IPersistStorage::SaveCompleted with a NULL if fSameAsLoad was TRUE; otherwise, we have to pass the IStorage of the new file we saved so that Polyline can reinitialize its pointers:


//From CCosmoDoc::Save
LPSTORAGE pIStorage;
BOOL fSameAsLoad;

//If Save or Save As under the same name, do Save.
if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
{
fRename=FALSE;
pszFile=m_szFile;
fSameAsLoad=TRUE;
}
else
{
hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
| STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

if (FAILED(hr))
return DOCERR_COULDNOTOPEN;

fSameAsLoad=FALSE;
m_pIStorage->Release();
m_pIStorage=pIStorage;
}

if (PERSIST_STORAGE==m_pp.psModel)
{
hr=m_pp.pIP.pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

if (SUCCEEDED(hr))
{
hr=m_pp.pIP.pIPersistStorage->SaveCompleted(fSameAsLoad
? NULL : m_pIStorage);
}
}
else
{
LPSTREAM pIStream;

hr=m_pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
| STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
, 0, 0, &pIStream);

if (SUCCEEDED(hr))
{
//This also works for PERSIST_STREAMINIT.
hr=m_pp.pIP.pIPersistStream->Save(pIStream, TRUE);
pIStream->Release();
}
}

Through this little bit of code, CoCosmo can handle an object with any persistence model except IPersistFile. To demonstrate this ability, we'll look at two implementations of Polyline: one with IPersistStorage and the other with IPersistStreamInit. But first, as a close to this section, note that CoCosmo never has occasion to place Polyline in the hands-off state, so it never calls IPersistStorage::HandsOffStorage. In addition, when saving a stream-based object, CoCosmo could fail to create the stream because of an out-of-memory condition. To be more robust, it would create and cache the stream when it found a stream-based object, just as a storage-based object would cache a pointer inside its InitNew or Load.