Implement IPersistStorage

It's best to first implement IPersistStorage on an embedded object because the first thing OleCreate or OleLoad will do is call member functions in this initialization interface. For the most part, the code is similar to the code we implemented for Polyline back in Chapter 8. Inside InitNew and Load, this implementation of IPersistStorage creates or opens streams as necessary, holding on to those pointers for low-memory saves, releasing those pointers as needed in HandsOffStorage, and so forth.

IPersistStorage::IsDirty is implemented by calling CFigure::FIsDirty, which in turn asks the document holding the figure if the document is dirty. The document, however, always returns FALSE when it is open in order to edit an embedded object. Why is this? Remember that an embedded object continually notifies its container about changes, so the object in the server's user interface and the object image in the container always match. From the user's point of view, the object is never actually dirty, although we still need to ask the container to save it before we close, as we'll see.

The core implementation of IPersistStorage::Load and IPersistStorage::Save delegates its functionality to CPolyline::ReadFromStream and CPolyline::WriteToStream. These functions contain code that I broke out of the implementations of CPolyline::ReadFromStorage and CPolyline::WriteToStorage that we added in Chapter 7. The core functionality of Cosmo's CPolyline class, then, stays exactly the same. All we've done here is reorganize that code to make it more accessible from an interface implementation.

We also handle conversion and emulation considerations inside Load and Save.2 First we handle emulation of the Polyline class because both Cosmo and the Polyline component from Chapter 19 share the same stream name and stream format. Thus, we need not make any special consideration. The conversion case is more interesting. If you read through the container side of these features in Chapter 17, you'll know that the container calls the function SetConvertStg before reloading a newly converted object. When we enter Load, we call GetConvertStg to see if this is, in fact, a conversion case. We also read whatever CLSID is in the storage so we can return it from IPersistStorage::GetClassID when we're doing emulation:


STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
{
if (PSSTATE_UNINIT==m_psState)
return ResultFromScode(E_UNEXPECTED);

*pClsID=m_pObj->m_clsID;
return NOERROR;
}


STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
{
[Validation code omitted]

//The next statement tells us whether we're
//coming from another class storage.
m_fConvert=(NOERROR==GetConvertStg(pIStorage));

ReadClassStg(pIStorage, &m_pObj->m_clsID);
§
}

From here we simply load the data as necessary from whatever streams are present in the storage. When we now go to save, we have to ensure that the storage format is converted to our own. The m_fConvert flag we saved in Load tells us to do this. We first write our own streams as necessary, delete the original object's streams (which amounts to nothing when we're converting a Polyline object because the streams are identical), update the format and user type in the storage, and turn off the convert bit:


STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
, BOOL fSameAsLoad)
{
§
if (m_fConvert)
{
UINT cf;

cf=RegisterClipboardFormat((*m_pObj->m_pST)[IDS_FORMAT]);
WriteFmtUserTypeStg(pIStorage, cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);

SetConvertStg(pIStorage, FALSE);
m_fConvert=FALSE;
}
§
}

After this process, the object's persistent storage will be fully a Cosmo figure no matter what it was originally. The code we've demonstrated in Cosmo is perhaps artificially simple because Cosmo and Polyline share the same storage formats. This might be true of different versions of your own server. It should be obvious, however, that handling different formats from other competitive servers, through both conversion and emulation, will involve the handling of more streams with different data, in both Load and Save. Nevertheless, the same pattern of operations holds. In conversion, load the storage that is there and write your own formats when saving. In emulation, you need to both load and save the same format.

To finish up IPersistStorage, the implementations of SaveCompleted and HandsOffStorage are pretty standard except for one thing. Just before we return from SaveCompleted, we make a call to any connected container's IAdviseSink::OnSave. (This happens through CFigure::SendAdvise, which we'll look at later.) This tells containers that a save has been completed, which some older containers (in particular Microsoft Excel 5) use to know that saving succeeded. The call is benign for containers that don't care otherwise, but it is a wise call to include for maximum compatibility.

2 This includes OLE 1 version handling. Again, see OLE1.WRI on the companion CD.