This writeup is about customizing the Win32 shell by means of shell extensions, OLE, and sundry dweebmongering. There won't be any code, at least.


In the post-Windows 95 Windows shell, a.k.a. "Windows Explorer", you can add features to the shell in a number of ways. A "drop handler" is one of these, and it works like this:

When you open an "explorer" window, you see your files. You can drag them around with the mouse. You may notice that when you drag a file over directories1 or certain files -- batch files, for example, or executables (programs) -- the file you drag over (we'll call it a "target") will be highlighted. If you drop your file on that "target", something will happen. If you drop a file on a directory, the file will be moved into that directory -- unless it's on a different drive, or unless the file you've dropped is an executable2). If you drop a file on a program or a batch file, the system will run the program or batch file, with your file or files as "command line arguments". In plain English: If you drop a text file on your MS Word executable (or on a "shortcut" to Word), Windows will open that text file in Word.

There's one constraint here: If you're dragging multiple files, the aggregate length of the full paths and names for all of those files must be less than 2048 characters. If it's more, the whole thing fails. This is absolutely moronic (when I tested it just now, I had 90 MB if memory free -- look, I bought the goddamn RAM, let's use it, okay?), but that's Windows for you.

If you've got Windows Scripting Host installed (with recent versions of IE, or versions of Windows from Windows 98 onward, you've got no choice in the matter), script files will often be highlighted just like batch files, executables, or directories.

A "drop handler" is what makes all of happen. A "drop handler" is a glob of code associated with a given file type. When you drag a file (A) over another file (B), the shell goes to the registry and looks to see if there's a drop handler for file B. If file B is a batch file, here's what it finds:

    [HKEY_CLASSES_ROOT\batfile\ShellEx\DropHandler]
    @="{86C86720-42A0-1069-A2E8-08002B30309D}"

This looks like gibberish, and it is, but it's meaningful gibberish. "batfile" is the definition of the "file type" for batch files. The batfile key has several keys under it. Those keys specify the icon that gets displayed with batch files, the menu commands that appear when you right click on a batch file, and finally shell extensions, "ShellEx". Under the ShellEx key is another key, DropHandler. That's our boy. The "default" value for that key is a CLSID. A CLSID is a 128-bit GUID ("globally unique identifier") which is associated with a "COM object". That "COM object" is the drop handler. It's contained in a DLL somewhere3, and it's the code that gets called to decide what happens when somebody drags a file over a batch file.

The drop handler for a given file type is notified when something is dragged over a file of that type, and when something is dropped on a file of that type. In the "drag over" "event", it can decide whether the "target" file should be highlighted or not. It passes that information back to the shell, which updates the display accordingly. In the "on drop" "event", it can decide what to do about that, too. It could do absolutely nothing, for example. It could also pop up a menu to let the user decide exactly what to do. If it's buggy, it might just throw a fit and crash the shell.

You can set this up for other files besides the usual ones we've mentioned. The Windows Scripting Host installer does it for JavaScript and VBScript files.

Now, what's interesting about all this is the potential for setting up the same kind of functionality with scripts written in other languages. This turns out to be fiendishly easy, because as of this writing (12/29/2001), both the batch file drop handler (CLSID as above) and the generic WSH drop handler (CLSID {60254CA5-953B-11CF-8C96-00AA00B8708C}) do what they ought to rather than something stupid: When they're activated, they get everything from the registry. If the registry knows how to run an awk script (you'll want the command in the registry to be '[awk path]\awk.exe -f "%1" -- %*', so the arguments will be passed sanely (I'm assuming you're using GNU awk, of course)), you can add a ShellEx\DropHandler key to the awk file type definition in the registry, just as we saw above in the batfile example. The drop handler will then work properly. That's not so bad, is it? Personally, by the way, I prefer the WSH handler to the batfile one, because of how it handles the 2048-character limitation: It gives you an informative error message, while the batfile handler feeds you some wild-eyed gibberish about not being able to "access the file".

While we're on the subject, the popular WinZip compression utility comes with a drop handler, too. Their drop handler is quite specific: When you drop one or more files on a zip file, the handler starts WinZip and adds those files to the zip file. It's neat.

For another "by the way", I assume you've noticed the "Send To" option in your Explorer context menu, right? Well, there are a few things to say about that, but to keep it quick, those "Send To" items are located in a directory somewhere (right click on the Start button, select "Open", and hit the backspace key when the Explorer window opens. You should see a SendTo subdirectory in there; double-click on that, and there you are). If one of those "Send To" items is a "shortcut" (and most are), the shell will use the drop handler for the target of the shortcut when you "send a file" to it. In other words, if you put a "shortcut" to a batch file in your "Send To" menu, and then you "send" a file to that "shortcut" by means of the context menu, the batch file drop handler will be called to start the batch file with the appropriate "sent" files on its command line. If you've put a "shortcut" to a Perl script in your "Send To" menu, now you know why it didn't work (unless you installed that ActiveState version of Perl, the one that integrates with WSH).


Writing Your Own

I mean, that's what you're thinking, right?

Okay, it's a COM object. You'll have to implement IDropTarget and IPersistFile::Load(). The other members of IPersistFile can be stubs, because they're never called. IPersistFile::Load() is called with the name of the "target" file; various IDropTarget members are called as appropriate (DragOver(), Drop(), etc.).

MSDN doesn't seem to have any example code for this, but there's a lucid and useful example at http://www.zdnet.com/pcmag/pctech/content/15/02/ut1502.004.html (but beware; if you "install" it, it'll replace the drop handler for .zip files, and it behaves idiotically (it deletes files after adding, and always smashes filenames to 8.3 tilde crap). Be sure you make a note of the CLSID for the current drop handler (if any) so you can restore that later).




1 Microsoft wants you to call directories "file folders". That's because it's an idiotic, nonstandard, and confusing term. They don't even use it consistently themselves.

2 If you drop someting into a directory on a different drive from where it lives, it will be copied instead of moved. If it's an executable, you'll get a "shortcut" to the executable, unless you're dragging multiple files, in which case executables will be moved or copied with the rest.

If you hold down the "alt" key while dragging, you'll always get a shortcut. If you hold down the shift key, the file will always be moved rather than copied, even if it's going to a different drive. If you hold down the control key, you'll always get a copy, even if you're dragging it to a different directory (or the same directory) on the same drive.

If you drag with the right mouse button rather than the left, you'll be given a popup menu when you drop the file(s). The menu will let you choose between copying, moving, creating a "shortcut", or cancelling the whole operation.

All of this labyrinthine behavior looks like a regular drop handler, but it appears to be built into the shell instead.

3 Ah, but where? That's easy:

    HKEY_CLASSES_ROOT\CLSID\{CLSID}\InProcServer32
    ...or...
    HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocHandler32
    ...or...
    HKEY_CLASSES_ROOT\CLSID\{CLSID}\LocalServer32

...and probably a few others. What they all have in common is that they're under a key named for the CLSID, and that key is located under HKEY_CLASSES_ROOT\CLSID\.

In the case of our batch file example, it's here:

    HKEY_CLASSES_ROOT\CLSID\{86C86720-42A0-1069-A2E8-08002B30309D}\InProcServer32
    @="%SystemRoot%\system32\shell32.dll"
    ThreadingModel="Apartment"