Pulling Rabbits from a Hat with STGM_CONVERT

Cosmo is capable of reading and writing two different versions of its files; in Chapter 1, both formats were typical MS-DOS files. For this chapter and later ones, Cosmo uses a compound file for its version 2 files, but it remains compatible with old files (both the version 1 files and the version 2 files generated by the Chapter 1 version of Cosmo) and is able to read and write the old formats. Cosmo preserves its old file-writing code for this reason, but reading files of either format can be centralized using the STGM_CONVERT flag. We could, of course, test a file with StgIsStorageFile first and call the appropriate code to handle whichever version of the file is present. Doubtless many applications have this code already. But let's see what we can do with OLE's automatic conversion.

When Cosmo sees a noncompound file while loading, it calls StgCreateDocfile, passing STGM_CONVERT instead of STGM_CREATE, and this causes OLE to open the file as a compound file with a single stream named "CONTENTS" (in uppercase). If this operation succeeds, the HRESULT returned from StgCreateDocfile will contain STG_S_CONVERTED, which is a success code (_S_) but different from NOERROR. Therefore, using the FAILED macro is a valid test for errors.


LPSTORAGE    pIStorage;
HRESULT hr;

hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
| STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

if (FAILED(hr))
{
if (STG_E_ACCESSDENIED==GetScode(hr))
[Try loading file using traditional file I/O.]
}

When using StgCreateDocfile for conversion, we purposely pass STGM_TRANSACTED ¦ STGM_READWRITE but never bother to commit anything. The semantics of STGM_CONVERT in StgCreateDocfile mean "convert the file now." If we use STGM_DIRECT in this case, the old file will be immediately overwritten with a compound file. By specifying STGM_TRANSACTED, we create the conversion in a separate temporary storage, leaving the original disk image unaffected. Calling IStorage::Commit would then change the actual file on the disk.

Because conversion is a potential Write operation, we must specify at least STGM_WRITE along with STGM_CONVERT. If the file is marked as read-only, however, StgCreateDocfile will fail with STG_E_ACCESSDENIED. In this situation, you can default to loading the file with old code that restricts you to read-only access. For this reason, Cosmo preserves its original CPolyline::ReadFromFile function as a backup.

It is no coincidence that I chose to use the stream name "CONTENTS" for Cosmo's native compound files. My choice means that the code in CPolyline::ReadFromStorage doesn't have to care whether the storage is a native compound file or a converted traditional file. It is, however, perfectly fine to use whatever stream names you want, especially if you need to distinguish between versions of data elsewhere in your code. The choice of "CONTENTS" in Cosmo is completely arbitrary, made only to match what OLE creates with STGM_CONVERT.

Note: STGM_CONVERT can be used only with StgCreateDocfile and StgCreateDocfileOnILockBytes. It is not supported with StgOpenStorage or StgOpenStorageOnILockBytes.