{"id":27523,"date":"2007-03-23T10:00:00","date_gmt":"2007-03-23T10:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2007\/03\/23\/excursions-in-composition-adding-rewind-support-to-a-sequential-stream\/"},"modified":"2007-03-23T10:00:00","modified_gmt":"2007-03-23T10:00:00","slug":"excursions-in-composition-adding-rewind-support-to-a-sequential-stream","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20070323-00\/?p=27523","title":{"rendered":"Excursions in composition: Adding rewind support to a sequential stream"},"content":{"rendered":"<p>\nHere&#8217;s a problem &#8220;inspired by actual events&#8221;:\n<\/p>\n<blockquote CLASS=\"q\">\n<p>\nI have a sequential stream\nthat is the response to a request I sent to a web site.\nThe format of the stream is rather messy; it comes with a\nvariable-length header that describes what type of data is being returned.\nI want to read that header and then\nhand the stream to an appropriate handler.\nBut the handlers expect to be given the stream in its entirety,\nincluding the bytes that I have already read.\nSince this is a sequential stream,\nI can&#8217;t change the seek position.\nHow can I &#8220;unread&#8221; the data and give the handlers what they want?\n<\/p>\n<p>\nRight now, I&#8217;m just closing the stream I get and issuing a second request.\nI give that second stream to the handler that I determined by\nparsing the first stream.\nOf course, this makes the rather unsafe assumption that\nthe server will send the same data stream back the\nsecond time, and I&#8217;m issuing twice as many\nrequests as I really need.\n<\/p>\n<p>\nI tried reading the entire stream into memory and creating\na new stream on that, but the data stream can be very big,\nand I feel bad allocating all that memory just so I can\n&#8220;unread&#8221; a few dozen bytes.\n<\/p>\n<\/blockquote>\n<p>\nAll the customer wants here is to be able to &#8220;unread&#8221; some bytes\nfrom a stream.\nThis is a perfect job for our\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2007\/03\/22\/1927891.aspx\">\ncomposite sequential stream<\/a>.\nThe first stream consists of the bytes we want to push back,\nand the second stream is the rest of the stream.\nHere&#8217;s an explanation in pictures.\nThe pink stream is the original stream returned from the web site,\nand the green stream remembers the bytes we want to push back.\n<\/p>\n<p>\nInitial state:\n<\/p>\n<table BORDER=\"0\">\n<tr>\n<td STYLE=\"border: solid .75pt black;background: lightgreen\">&nbsp;<\/td>\n<td STYLE=\"border: solid .75pt black;background: pink\">ABCDEFGHIJK&#8230;<\/td>\n<\/tr>\n<\/table>\n<p>\nAfter reading a few bytes:\n<\/p>\n<table BORDER=\"0\">\n<tr>\n<td STYLE=\"border: solid .75pt black;background: lightgreen\">&nbsp;ABCD<\/td>\n<td STYLE=\"border: solid .75pt black;background: pink\">EFGHIJK&#8230;<\/td>\n<\/tr>\n<\/table>\n<p>\nAfter reading a few more bytes:\n<\/p>\n<table BORDER=\"0\">\n<tr>\n<td STYLE=\"border: solid .75pt black;background: lightgreen\">&nbsp;ABCDEFG<\/td>\n<td STYLE=\"border: solid .75pt black;background: pink\">HIJK&#8230;<\/td>\n<\/tr>\n<\/table>\n<p>\nFinally, we have determined the handler, but the handler expects\nto start reading from the &#8220;A&#8221;, so we will take these two streams\nand concatenate them so they look like\none stream again:\n<\/p>\n<table BORDER=\"0\" STYLE=\"border: solid .75pt black;background: lightblue\">\n<tr>\n<td STYLE=\"border: solid .75pt black;background: lightgreen\">&nbsp;ABCDEFG<\/td>\n<td STYLE=\"border: solid .75pt black;background: pink\">HIJK&#8230;<\/td>\n<\/tr>\n<\/table>\n<p>\nOkay, let&#8217;s do it.\nAll we have to worry about is filling the green stream\nwith the bytes that we read out of the pink stream;\nwe&#8217;re going to use\n<code>CConcatStream<\/code> to do the composition at the end.\n<\/p>\n<pre>\nclass CRewindStream : public CROSequentialStreamBase\n{\npublic:\n CRewindStream(ISequentialStream *pstm);\n ISequentialStream *Rewind();\n \/\/ *** ISequentialStream ***\n STDMETHODIMP Read(void *pv, ULONG cb, ULONG *pcbRead);\nprotected:\n ~CRewindStream();\n bool m_fRewound;\n IStream *m_pstm1;\n ISequentialStream *m_pstm2;\n};\nCRewindStream::CRewindStream(ISequentialStream *pstm)\n : m_fRewound(false), m_pstm2(pstm)\n{\n CreateStreamOnHGlobal(NULL, TRUE, &amp;m_pstm1);\n m_pstm2-&gt;AddRef();\n}\nCRewindStream::~CRewindStream()\n{\n if (m_pstm1) m_pstm1-&gt;Release();\n m_pstm2-&gt;Release();\n}\nHRESULT CRewindStream::Read(void *pv, ULONG cb, ULONG *pcbRead)\n{\n ULONG cbRead = 0;\n HRESULT hr;\n if (m_fRewound) {\n  hr = E_FAIL;\n } else if (!m_pstm1) {\n  hr = E_OUTOFMEMORY;\n } else {\n  hr = m_pstm2-&gt;Read(pv, cb, &amp;cbRead);\n  if (SUCCEEDED(hr)) {\n   hr = m_pstm1-&gt;Write(pv, cbRead, NULL);\n  }\n }\n if (pcbRead) *pcbRead = cbRead;\n return hr;\n}\nISequentialStream *CRewindStream::Rewind()\n{\n if (!m_pstm1 || m_fRewound) return NULL;\n m_fRewound = true;\n const LARGE_INTEGER li0 = { 0, 0 };\n m_pstm1-&gt;Seek(li0, STREAM_SEEK_SET, NULL);\n return new CConcatStream(m_pstm1, m_pstm2);\n}\n<\/pre>\n<p>\nOur <code>RewindStream<\/code> takes a sequential stream\nand creates a memory stream via <code>CreateStreamOnHGlobal<\/code>\nto remember the parts that we read so we can stick them back\nwhen it&#8217;s time to rewind.\n<\/p>\n<p>\nWhen reading from the stream, we read from the sequential stream\nand append the result to the memory stream before returning it.\nIn this manner, each byte read from the head of the pink stream\ngets appended to the end of the green stream.\n<\/p>\n<p>\nWhen it&#8217;s time to rewind, we seek the memory stream back to the\nbeginning and create a concatenated stream out of the memory\nstream and the unread portion of the sequential stream.\n<\/p>\n<p>\nHere&#8217;s a simple program that illustrates our new rewindable sequential\nstream:\n<\/p>\n<pre>\nint __cdecl _tmain(int argc, TCHAR **argv)\n{\n CoInitialize(NULL);\n IStream *pstmFile;\n if (SUCCEEDED(SHCreateStreamOnFile(argv[1], STGM_READ,\n                                    &amp;pstmFile))) {\n  CRewindStream *pstmRewind = new CRewindStream(pstmFile);\n  if (pstmRewind) {\n   char ch;\n   ULONG cb;\n   while (SUCCEEDED(pstmRewind-&gt;Read(&amp;ch, 1, &amp;cb)) &amp;&amp; cb) {\n    printf(\"Header: '%c'\\n\", ch);\n    if (ch == ' ') {\n     ISequentialStream *pstmRewound = pstmRewind-&gt;Rewind();\n     if (pstmRewound) {\n      PrintStream(pstmRewound);\n      pstmRewound-&gt;Release();\n     }\n     break;\n    }\n   }\n   pstmRewind-&gt;Release();\n  }\n  pstmFile-&gt;Release();\n }\n CoUninitialize();\n return 0;\n}\n<\/pre>\n<p>\nFor illustration purposes, let&#8217;s assume that the header\nends when we find a space.\nWe create a stream on the file whose name is passed on the\ncommand line and put it inside a <code>CRewindStream<\/code>.\nWe then read from that stream a byte at a time until we\nfind that space.\n(To prove that we&#8217;re really rewinding, we also print\neach byte from the header as we encounter it.)\nOnce we find the space, we ask the <code>CRewindStream<\/code>\nto create a new stream that represents the original stream\nrewound back to the beginning.\nWe pass that stream to the <code>PrintStream<\/code> helper\nfunction from last time, to prove that the resulting stream\nreally is the entire file contents starting from the beginning.\n<\/p>\n<p>\nNotice that we consumed only as much memory as necessary to\nremember the parts of the stream that needed to be replayed.\nEven if the file were a megabyte in size, we only need to remember\nthe bytes that we read up until we decided that we were finished\nreading the header.\n<\/p>\n<p>\nNow, this isn&#8217;t the most beautiful implementation of a rewindable\nstream, but it was convenient for expository purposes.\nI leave you to make as many prettifications as meet your aesthetic\nrequirements.\n<\/p>\n<p>\nExercise: Discuss what would be needed in order to support\nrewinding more than once and the performance consequences thereof.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s a problem &#8220;inspired by actual events&#8221;: I have a sequential stream that is the response to a request I sent to a web site. The format of the stream is rather messy; it comes with a variable-length header that describes what type of data is being returned. I want to read that header and [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-27523","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Here&#8217;s a problem &#8220;inspired by actual events&#8221;: I have a sequential stream that is the response to a request I sent to a web site. The format of the stream is rather messy; it comes with a variable-length header that describes what type of data is being returned. I want to read that header and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/27523","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=27523"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/27523\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=27523"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=27523"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=27523"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}