What a drag: Dragging a virtual file (IStream edition)

Last time, we saw how to drag a virtual file whose contents are expressed as a block of bytes in memory (HGLOBAL). Often, a block of bytes is not a convenient way to express the contents of a virtual file. You might prefer to express it as a stream. For example, the contents might be dynamically generated (say by the output of an algorithm), or it might come in from an external source (say, a web page that is being downloaded). Let's take our program from last time and convert it to return the file contents in the form of a stream. The first change we need to make is to our constructor, telling it to report file contents as a stream rather than as an HGLOBAL:

#include <shlwapi.h> // for SHOpenRegStream

CTinyDataObject::CTinyDataObject() : m_cRef(1)
               TYMED_ISTREAM, /* lindex */ 0);

Next, we need to produce that stream and its corresponding descriptor in our IDataObject::GetData handler:

HRESULT CTinyDataObject::GetData(FORMATETC *pfe, STGMEDIUM *pmed)
  ZeroMemory(pmed, sizeof(*pmed));

  switch (GetDataIndex(pfe)) {
    ZeroMemory(&fgd, sizeof(fgd));
    fgd.cItems = 1;
    pmed->tymed = TYMED_HGLOBAL;
    return CreateHGlobalFromBlob(&fgd, sizeof(fgd),
                              GMEM_MOVEABLE, &pmed->hGlobal);

    pmed->tymed = TYMED_ISTREAM;
    pmed->pstm = SHOpenRegStream(HKEY_LOCAL_MACHINE,
       TEXT("~MHz"), STGM_READ);
    // set the stream position properly
    if (pmed->pstm) {
      LARGE_INTEGER liZero = { 0, 0 };
      pmed->pstm->Seek(liZero, STREAM_SEEK_END, NULL);
    return pmed->pstm ? S_OK : E_FAIL;

  return DV_E_FORMATETC;

Of course, in real life, you would use a more interesting stream than your CPU speed. I just chose that one as an example.

As with our HGLOBAL-based data object, you can drop this data object onto an Explorer folder to create a file, into an Outlook message to create an attachment, and anywhere else a program supports the shell virtual file transfer model. And as with the HGLOBAL example, you can set various optional information in the FILEGROUPDESCRIPTOR in order to make the transfer go more smoothly, particularly the expected stream size. But I won't go into it because the theme of this series is "It's the least you can do".

But already you know enough to solve this customer's problem:

We need to know what directory the user dropped a file onto. We need to transfer data from another computer, so what we do is have the user drag a single dummy file, and then once we find out where the user dropped the dummy file, we can go in, delete the dummy file, and start transferring the data from the remote computer and saving it into real files in the destination directory.

Next time, we'll look at the final storage medium that can be used for file transfer, the TYMED_ISTORAGE.

Comments (12)
  1. Nathan_works says:

    The customer’s comment seems more like your regular entries where the correct plan of action is not to tell the customer how to do what they ask, but get them to say what they really want. By backing away from the specific request to a more big-picture "what are you trying to do/accomplish," since what they originally ask for is usually wrong or doesn’t make sense..

  2. Koro says:

    There’s also the (very useful) CreateStreamOnHGlobal function (so you never give the HGLOBAL directly)

  3. herd says:

    Does this work with virtual folder trees as well?

    I could imagine that the cFileName member of the FILEDESCRIPTOR structure could hold subfolder names like ".SubFldFile.txt" and the shell would create the folder on the fly if it does not exist already, else show me the [this folder already contains a folder named ‘SubFld’…] dialog.

  4. BryanK says:

    The customer can replace their "dummy file" with a FILEGROUPDESCRIPTOR with cItems equal to the count of files, and with each item being an IStream pointer (where reads from the stream give data from the original files — I bet there’s a shell interface to create this type of IStream object, but I’d have to look it up).

    This would give the same effect as dragging multiple files from a folder, but since the source isn’t Explorer, the user doesn’t actually have to go select all those files.  They can simply drag some "single object" from the customer’s program instead.

    (This will also work when the target isn’t Explorer (aka isn’t a directory).  What they’re asking for (finding the target path) won’t work in that case.)

    It *sounds* like what they want to accomplish is copying a bunch of files while only making the user drag a single item.  But of course, if that’s not really it, then this solution won’t help either.  :-)

  5. Chris says:

    Thanks for this series, really helpful

  6. non-english says:

    What does the phrase "What a drag" mean?

  7. clipboard says:

    Is this info useful for programs to store virtual files in the clipboard? Is that possible?

  8. MadQ1 says:

    @non-english: In this particular case it is definition #18 at http://dictionary.reference.com/browse/drag

    "(noun) 18. Slang. someone or something tedious; a bore: It’s a drag having to read this old novel."

    Raymond is really just using it as a rethoric device, though.

  9. Martin says:


    I like the way you explain "what a drag" means but leave it to "non-english" to work out what rhetorical device means.

  10. MadQ1 says:

    Teach someone how to fish ;-) If he followed the link, he was already at the right place. Also, if he speaks any germanic- or latin-based language (or Greek,) he’ll know what it means.

  11. Andreas Nilsson says:

    Any recommendations / hints to allow users to drag-n-drop a IShellLink (created-on-the-fly as an IStream) from an app-window to the desktop?

    Thanks in advance,

    Andreas Nilsson

  12. BryanK says:

    You could create whatever kind of stream (probably backed by a fairly large amount of memory?  or possibly something that reallocates memory on the fly as needed), create an instance of ShellLink (the default object that implements IShellLink), QI for IPersistStream, then save the IShellLink to the stream you created above.  Then hand that stream off to the DnD interfaces.

    At least, I *think* that should work (though I don’t know how the shell would know if the object being passed to it is also a shell link: maybe there’s a flag in the FORMATETC stuff).  Never tried it.  :-)

Comments are closed.