Earlier in this chapter, I mentioned that a strong parallel exists between traditional file I/O functions (in both the Windows API and the C run-time library) and the member functions of the IStream interface. In the implementation of Cosmo's CPolyline class, we can see these similarities by comparing its ReadFromFile member used in the Chapter 1 version of Cosmo to the ReadFromStorage member used here. The two functions, ReadFromFile and ReadFromStorage, are shown side by side here to illustrate the similarities between the two implementations:
LONG CPolyline::ReadFromFile LONG CPolyline::ReadFromStorage
(LPSTR pszFile) (LPSTORAGE pIStorage)
{ {
OFSTRUCT of; HRESULT hr;
HFILE hFile; LPSTREAM pIStream;
POLYLINEDATA pl; POLYLINEDATA pl;
UINT cb=-1; ULONG cb=-1;
UINT cbExpect=0; ULONG cbExpect=0;
LARGE_INTEGER li;
if (NULL==pszFile) if (NULL==pIStorage)
return POLYLINE_E_READFAILURE; return POLYLINE_E_READFAILURE;
hFile=OpenFile(pszFile, &of hr=pIStorage->OpenStream("CONTENTS", 0
, OF_READ); , STGM_DIRECT | STGM_READ
| STGM_SHARE_EXCLUSIVE, 0
, &pIStream);
if (HFILE_ERROR==hFile) if (FAILED(hr))
return POLYLINE_E_READFAILURE; return POLYLINE_E_READFAILURE;
cb=_lread(hFile, (LPSTR)&pl hr=pIStream->Read((LPVOID)&pl
, 2*sizeof(WORD)); , 2*sizeof(WORD), &cb);
_llseek(hFile, 0L, 0); LISet32(li, 0);
pIStream->Seek(li, STREAM_SEEK_SET, NULL);
if (2*sizeof(WORD)!=cb) if (FAILED(hr) || 2*sizeof(WORD)!=cb)
{ {
_lclose(hFile); pIStream->Release();
return POLYLINE_E_READFAILURE; return POLYLINE_E_READFAILURE;
} }
[Code here to calculate cbExpect [Code here to calculate cbExpect
based on version number] based on version number]
cb=_lread(hFile, (LPSTR)&pl hr=pIStream->Read((LPVOID)&pl
, cbExpect); , cbExpect, &cb);
_lclose(hFile); pIStream->Release();
if (cbExpect!=cb) if (cbExpect!=cb)
return POLYLINE_E_READFAILURE; return POLYLINE_E_READFAILURE;
DataSet(&pl, TRUE, TRUE); DataSet(&pl, TRUE, TRUE);
return MAKELONG(pl.wVerMin return MAKELONG(pl.wVerMin
, pl.wVerMaj); , pl.wVerMaj);
} }
We can see how a call to OpenFile maps to a call to IStorage::OpenStream; we treat the storage object as we'd treat the file system. Then all of the old file I/O functions called with the file handle map to IStream calls; in this case, the standard return type of HRESULT means that we have to pass pointers to out-parameters of IStream functions that are generally the straight return values of traditional file I/O calls (such as the cb returned from _lread).
One glaring difference between traditional files and streams is seeking. Because the Structured Storage definition of IStream allows a stream to contain up to 264 addressable bytes of data, we can't just use a DWORD to indicate the new seek offset. For this reason, OLE defines the type LARGE_INTEGER, a 64-bit value of two LONGs, together with the macro LISet32, which fills such a structure with a 32-bit value:
LARGE_INTEGER li;
LISet32(li, 0);
pIStream->Seek(li, STREAM_SEEK_SET, NULL);
There is also a ULARGE_INTEGER, composed of two (unsigned) DWORDs, with the associated macro ULISet32. The third parameter to IStream::Seek could be a ULARGE_INTEGER, which would receive the prior seek offset before the call. In the code shown above, the NULL means that we're not interested in this information.