Reading an RTF Stream

Suppose you’ve gotten a stream from WrapCompressedRTFStream and want to read what’s in it. Your code might look something like this:

 #define MAXBYTES 256
 void ProcessStream(LPSTREAM lpStream)
 {
    HRESULT hRes = S_OK;
    BYTE bBuf[MAXBYTES];
    ULONG ulNumBytes = 0;
    LARGE_INTEGER li = {0};
  
    hRes = lpStream->Seek(
       li,
       STREAM_SEEK_SET,
       NULL);
  
    if (S_OK == hRes) do
    {
       hRes = lpStream->Read(
          bBuf,
          MAXBYTES,
          &ulNumBytes);
  
       if (ulNumBytes > 0)
       {
          // do something with the bytes read
       }
    }
    while (ulNumBytes == MAXBYTES);
 }

Note that in the do/while loop, the exit condition is when ulNumBytes is not equal to MAXBYTES, the number of bytes requested. This is consistent with the documentation for ISequentialStream::Read, which states:

“If the number of bytes returned is less than the number of bytes requested, it usually means the Read method attempted to read past the end of the stream.”

However, is this a best practice? When I took a look at our implementation of Read, I found the following comment:

“pcbRead < cb is not an error and does not necessarily mean the end of the file has been reached.”

If this is case, then your exit condition should be when ulNumBytes is zero. I’ve never seen WrapCompressedRTFStream behave this way in practice (and in fact, the code above was cribbed from MFCMAPI), but I have had a customer report it.

I did observe this behavior from a different stream though. In the old EDK, there was a function, HrTextFromCompressedRTFStreamEx, which works similar to WrapCompressedRTFStream. Instead of producing an unwrapped RTF stream, it goes one step further and converts the RTF to plain text. Normally, for EDK functions you can look at the source and see what’s going on, but this function is one of the few in the EDK that does not include source. The only way to call this function is to include the library rtflib32.lib, which shipped only in the EDK. So I’ll just have to tell you how the function works.

You might think this function works by calling WrapCompressedRTFStream, then parsing the resulting RTF, but it does not. Instead, it actually does all of the unwrapping and parsing in one pass over the source stream. The problem here is the parsing is somewhat rudimentary. There are a number of conditions that it can hit within the RTF to cause it to say “I’ve had enough – try again later”. It’ll output whatever it’s read so far, usually fewer bytes than were requested, without any indication of an error. If you call back in to it, it’ll give you more. One of the conditions I know of to cause this is if HrTextFromCompressedRTFStreamEx thinks it has detected a change in the code page.

So – if you’re working with streams in MAPI, the best practice here is to read until you can’t read any more. Here’s the corrected code (MFCMAPI will be fixed in the next update):

 #define MAXBYTES 256
 void ProcessStream(LPSTREAM lpStream)
 {
    HRESULT hRes = S_OK;
    BYTE bBuf[MAXBYTES];
    ULONG ulNumBytes = 0;
    LARGE_INTEGER li = {0};
  
    hRes = lpStream->Seek(
       li,
       STREAM_SEEK_SET,
       NULL);
  
    if (S_OK == hRes) do
    {
       hRes = lpStream->Read(
          bBuf,
          MAXBYTES,
          &ulNumBytes);
  
       if (ulNumBytes > 0)
       {
          // do something with the bytes read
       }
    }
    while (ulNumBytes > 0);
 }