The stream pointer position in IDataObject::GetData and IDataObject::GetDataHere is significant


An oft-overlooked detail of the IData­Object::Get­Data and IData­Object::Get­Data­Here methods is the position of the stream pointer when the result is a stream. These rules are buried in the documentation, so I'm going to call them out louder.

Let's look at IData­Object::Get­Data first.

If IData­Object::Get­Data returns a stream, then the stream pointer must be positioned at the end of the stream before the stream is returned. In other words, the last thing you do before returning the stream is seek it to the end. The contents of the data object are assumed to extend from the start of the stream to the stream's position as returned by IData­Object::Get­Data. (And no, I don't know why this rule exists.)

I messed this up in my demonstration of how to drag a stream. Let's fix it.

  case DATA_FILECONTENTS:
    pmed->tymed = TYMED_ISTREAM;
    pmed->pstm = SHOpenRegStream(HKEY_LOCAL_MACHINE,
       TEXT("Hardware\\Description\\System\\CentralProcessor\\0"),
       TEXT("~MHz"), STGM_READ);
    if (pmed->pstm) {
      LARGE_INTEGER liZero = { 0, 0 };
      pmed->pstm->Seek(liZero, STREAM_SEEK_END, NULL);
    }
    return pmed->pstm ? S_OK : E_FAIL;
  }

But what if you don't know the stream size? For example, what if the stream is coming from a live download? What if the stream doesn't support seeking? What if the stream is infinite? In those cases, you don't really have a choice. You just leave the stream pointer at the beginning and hope for the best. (Fortunately, at least in the case of virtual file content, the shell is okay with people who leave the stream pointer at the start of the stream. Probably for reasons like this.)

There is a similar detail with IData­Object::Get­Data­Here: If you are asked to produce the data into an existing stream, you should write the data starting at the stream's current position and leave the stream pointer at the end of the data you just wrote.

Comments (5)
  1. Ian Boyd says:

    Well damn.

    I have some applications to fix.

  2. jps says:

    I have some code to fix too...

  3. cheong00 says:

    I know the rule still exist, but is there any known cases this is relevant?

    Just like what you said, there are kinds of stream the have size unknown or simply don't support seeking. I think whatever OLE object that use this should expect to work when stream pointer position is not at the end.

  4. Matt says:

    Since the only thing a readonly stream seeked to the end can do is seek elsewhere before reading, doesn't this rule realistically translate as "You can't trust the pointer position of the output of this function, set it yourself manually before reading"?

    [It also means "Make sure to set it properly on the production side, in case your consumer actually uses the stream position to make decisions." For example, the consumer might be COM itself, who is using the stream position to decide how big a HGLOBAL to allocate in order to serialize your data object when you call OleFlushClipboard. -Raymond]
  5. Joker_vD says:

    I always thought the point of the stream was that its size is unknown, that it's not just an array with an index variable. Heck, SICP introduces streams as "suspended lists", meaning that you have a buffered part and a mysterious function that will produce the remaining part.

Comments are closed.

Skip to main content