You already know the answer since you do it yourself


A customer was writing a program that performed virtual drag/drop. They were using the IStream technique but found that many applications don’t support drag/drop of virtual content. They support only CF_HDROP. What’s more, often these applications query for CF_HDROP on DragEnter not because they want to access the file, but just because they want to get the file names (for example, because they want to put up the no-entry cursor if the file types are not ones the application supports).

Given that we want to be able to drop content onto applications which do not support drag/drop of virtual content, we have the problem of knowing exactly when to generate the content into a temporary file. If we generate the content too soon, then we may end up going to a lot of effort of creating a file that won’t actually be used; if we generate it too late, then the application will try to open the file and find that it isn’t there. When is the correct moment to generate the content? Is there some set of rules by which applications which do not support virtual drag/drop indicate whether they are obtaining the CF_HDROP just to see the names of the files, and whether they are obtaining it because they want to go open the files?

If you think about it, you already know the answer to this question because you already do it yourself when you write code that operates the client side of the drag/drop contract. When you write your program that accepts CF_HDROP content, do you use any special signal to tell the data object, “Hey, I’m asking for CF_HDROP just because I want the file names, but I promise not to try to open the files yet”? No, you don’t, so why would you expect any other application to? Even if there were rules surrounding this signaling protocol, the fact that they are widely ignored (because even you yourself ignore them) means that you can’t rely on the client performing them anyway.

The rule for CF_HDROP is that at the moment you offer CF_HDROP content, the files must already exist. The CF_HDROP clipboard format was created by File Manager in Windows 3.1, and File Manager did not support virtual content. The only thing it knew how to drag and drop was files, and the only thing it could drag was files that already existed.

In a sense, the rules around CF_HDROP were not so much codified by rule as codified by circumstance. Since the only things you could drag were files that already existed, those became the de facto rules for CF_HDROP.

Sorry.

Note that there is a little you can do: If an application calls QueryGetData for CF_HDROP, that does not force you to create the file content yet, because QueryGetData is just a yes/no query as to whether you could produce CF_HDROP if asked. You’re not being asked to do so, so you can just say “Why yes, I have files” even though you don’t yet. It’s only at the GetData that you have to return a list of file names and therefore must create the files.

Most applications are kind enough to use QueryGetData when they are not interested in the files yet but only in the potential for files, but there are some which use GetData on the DragEnter, and those applications will force you to commit to creating the content even if the user ultimately abandons the operation without dropping. (You could try deferring the creation until your IDropSource::QueryContinueDrag returns DRAGDROP_S_DROP, but programs which sniff the file contents during DragEnter will not find the file there, and they probably won’t like that. It also doesn’t work if the transfer is done via copy/paste rather than drag/drop—and you need copy/paste support for accessibility—since your drop source is not active during a copy/paste.)

Exercise: How do you know when it’s safe to delete the temporary files?

Comments (16)
  1. Dan Bugglin says:

    I don't know enough about dragging and dropping to answer the Exercise, but I'm going to take an educated guess and say you DON'T know.  The best solution may be to store them in %TEMP% and let Disk Cleanup clear it away at some point (or check yourself periodically and clear away any temporary files you made older than the last boot, which IMO is a reasonable metric).

    The logic I'm using is the same logic that web browsers use when you tell them to "Open" a file rather than save it (at least, last I checked this is what they do?).  You can't really know for sure when the file isn't going to be used anymore.  You could wait to see when the invoked application closes the file handle, but then maybe it's poorly coded and will reopen it a few seconds later.  Perhaps you wait until the application closes, but what if the associated program is simply a stub that launches the real application that handles the file?  And then of course maybe the user completely closes the file/app, changes their mind and tries to reopen it using an MRU list.

    Since there is no standard mechanism to say "OK I really am done with this file now" to a web browser or source of a virtual drop (AFAIK) I would say there IS no way to know.  However if a file goes for a "reasonable" period of time without access it's probably safe then, and a reboot is a pretty good way to be absolutely certain the application is completely closed and done with the file.

    Of course if the user dropped a music clip onto a media player app and then tried to add a reference to the temporary file to their media library, they would then expect it to persist between reboots… there's always an edge case.

  2. Joshua says:

    Universal clipboard is a mess. Just sayin'

  3. Ben Voigt says:

    MSDN says: "A window registers as a clipboard format listener by calling the AddClipboardFormatListener function. When the contents of the clipboard change, the window is posted a WM_CLIPBOARDUPDATE message."

    So you can detect when the user replaces your clipboard content with a new cut/copy, and then you can delete the temporary files.

    Some applications also call EmptyClipboard if they still have data on the clipboard when they exit, allowing the files to go away also.

    [Not necessarily. The destination app may still be using the file at the point the user copies something else to the clipboard. For example, a program accepts the drop and starts a long-running Import operation. To stay responsive, the app declares a successful drop and returns to its message loop, while continuing the Import in the background. You have no idea when that Import is complete. -Raymond]
  4. jader3rd says:

    If files had a SafeToDeleteOnDate attribute, it'd be possible to know when it's safe to clean them up.

    [Awesome. What date should you put on your temporary files? -Raymond]
  5. Joshua says:

    [Awesome. What date should you put on your temporary files? -Raymond]

    In this case, about now + 10 minutes. Of course there's always that odd program that doesn't know it's a temporary file and decides to keep a reference to it. Since that case isn't solvable, I refuse to worry about it.

    Incidentally, I've wished for such a thing quite a few times where a file would simply go away after a few minutes. I just can't be bothered enough to throw kernel into test mode for a device driver [to make files go away while they're still opened] (not enough $$$$ for driver signing).

  6. Gabe says:

    Joshua: If you want a file to go away when you're done with it, just mark it "delete on close" and forget about it.

  7. Joshua says:

    @Gabe: That's what I'm doing now, but Acrobat Reader is a bit too touchy about it.

    @Adam V: Nice link.

  8. Regarding the exercise:  You don't.  Keep them around for a few minutes or more – however long you think they need to be around before it will *probably* be safe to delete (there are no guarantees!).  Depending on the application, you might even want to never delete the files.  The receiving app might not even touch them until after the drag/drop operation has finished.  The file could be very large and if the receiving app synchronously reads the file during the drag/drop operation, it will hang the sending app too – because the drag/drop API seems to require that the sending app synchronously wait for the drop target to finish.  Nobody wants to see the *sending* app get hung too on a file open, so a simple solution as a drag/drop target is to PostMessage a custom message indicating that some files were dropped and need to be opened, and then immediately return from the drop target.  (This is how I solved the problem, anyway.  I'm guessing Windows Explorer does something similar, seeing as how it doesn't hang the sending app either when you drag/drop files for a big file copy operation – it looks like it kicks off an asynchronous file copy and then immediately returns.)  End result?  The sending app has no clue when it is safe to delete.

    I had to write some drag/drop related code (also clipboard, since the new clipboard uses IDataObject too) for a program that organized images.  This turned into a massive pain in the rear.  I had to write support for multiple image formats in multiple types of storage, and write support for file drag/drop as well in multiple types of storage, and test compatibility with several different pieces of software (including Microsoft's software) whose developers did not go to the lengths I did for ensuring drag/drop compatibility.

    In my opinion, the API got *waaaay* too complicated for its own good.  Too many different formats, and too many different ways of storing the data.  And each sending/receiving app has to have code written to support them all if they want good compatibility.  It's all nice-sounding in a pie-in-the-sky sort of sense – every app can transfer data in the best format/storage appropriate for the situation.  This is a perfect world for a sending app, and the worst nightmare for a receiving app.  Back to reality, not everyone provides all that support, which means we end up with today's situation where you can't drag/drop to/from some apps.  (One of my annoyances: the refusal of some software, like instant messaging clients, to accept file attachments from Outlook.  Sending a file attachment to a friend involves saving the image to a temporary file and then drag/dropping that.)

    What the API really needed was a standard Microsoft-provided interface for translating IDataObject data formats & storages from one type to another.  For example, if the sending app provided a filename but the receiving app expected an IStream, then the proposed interface would create an IStream that wraps the file.  It could do things like translate between image formats & storages too (e.g. device dependent bitmap to device independent bitmap, and vice versa).  Solid support for drag/dropping all the different format and storage combinations in the .NET Framework would have greatly helped, too.

    Even the .NET Framework and MS Office teams can't get it right.  Data objects can have either device dependent bitmaps, or device independent bitmaps (DIB), or both.  The .NET Framework only supplies device dependent bitmaps – not DIBs.   But MS Word and OneNote only accept DIBs – not device dependent bitmaps!  End result: you can't drag/drop an image from a .NET Framework app into MS Office.  I filed a report here: connect.microsoft.com/…/652248 but apparently it's by design and I'm guessing the probability of a fix is low.  Talk about right hand not talking to left in a big organization…  The NETFX team probably thinks "not my problem, it's Office's problem!"  And the reverse for the MS Office team (for whom I could only provide feedback via the anonymous black-hole webform which was probably ignored).  Attitudes like this help kill the big picture of having MS software being integrated into a nice package / ecosystem.

    If Microsoft can't get drag/drop working between their own products, there's *no way* most 3rd-party developers are going to get it right.  (For a .NET developer to do it right would involve rewriting some significant portions of the .NET Framework, seeing as how the framework maintainers aren't going to do anything about it anytime soon despite the fact they are up to version 4.0 of the framework now.)

  9. Oh, drag and drop. I hope DDE is not part of it.

    Just a couple days ago, I had IE hanging when trying to open favorites, and WinZip hang while executing shell commands. Turned out the "music player and music store access program" was hanging. Welcome to wonderful world of DDE (un)cooperation.

  10. @Gabe:

    DELETE_ON_CLOSE requires that drop targets opened the files with FILE_SHARE_DELETE, which few apps do.

  11. Anonymous Coward says:

    I suppose it should be possible to write a file system filter driver that allows applications to access virtual content by path. But personally, I consider software that doesn't support virtual drops ‘broken’ (at least where drag & drop is concerned) and wouldn't go out of my way to write involved and (as discussed above, and possibly except for the filter driver) inherently buggy workarounds.

  12. FNunes says:

    At the next boot of the OS, when you application starts of if you want to be "aggressive" when you application closes ?

    [That may be too soon. The application you dropped the file onto may have embedded a link. You can't delete the file as long as that link still exists. -Raymond]
  13. Deduplicator says:

    So without perfect_knowledge_TM you can never remove those files? Memory leak heaven!

  14. @Ben: "So you can detect when the user replaces your clipboard content with a new cut/copy, and then you can delete the temporary files.

    Some applications also call EmptyClipboard if they still have data on the clipboard when they exit, allowing the files to go away also."

    No.  Here's why:

    1.  Copy some big data to temporary files / clipboard using your app.
    2.  User pastes your files into Windows Explorer.

    3.  Windows Explorer kicks off a background thread to do the asynchronous file copy.

    4.  Now the user copies something else.

    5.  As you propose, your app tries to delete temporary files, thinking that the user has copied something else and that the files aren't needed.  But the files are still in use.

    6.  Your app might fail, since Windows Explorer would have a file open and locked so it can be copied.

    7.  Windows Explorer would fail, since you probably successfully deleted some files that it had enumerated for copying.

    For smaller files, the file paste might be fast enough that the user couldn't copy something else fast enough.  So you might get away with it most of the time.  But it's still a race condition.

  15. Kevin says:

    Can you just leave the files in (a subdirectory of) %TEMP%?  If the receiving app leaves them there it shouldn't be too surprised that they eventually vanish.  But on the other hand, a lot of app developers are stupid, so sooner or later this would result in data loss.

Comments are closed.