June 19th, 2017

How do I set the initial directory of the File Open dialog to a virtual directory?

A customer reported that they had been setting the lpstrInitialDir member of the OPENFILENAME structure to "::{645FF040-5081-101B-9F08-00AA002F954E}" in order to have the dialog’s default directory be the Recycle Bin. (I am not making this up.) They reported that this stopped working in Windows Vista and wanted to know why, and what they can do about it.

The lpstrInitialDir is supposed to be the name of a directory. Virtual folders are not directories. It so happened that virtual directories were accepted in Windows XP due to a bug: The file dialog uses the Path­Combine function¹ to combine the current directory with the lpstrInitialDir. The Path­Combine function checks whether the second parameter (lpstrInitialDir) is an absolute or relative path, and in Windows XP it had a bug that if the second parameter’s second character was a colon, then it assumed that the path was absolute. It did this without verifying that the first character was a letter.

Windows Vista fixed this bug in the Path­Combine function, which means that if you passed an lpstrInitialDir of "::{...}", the function said, “Oh, you almost fooled me there, but I’m onto you. That is not an absolute path. So I will combine it with the first parameter (the current directory).” The file dialog then says, “Let’s go to that folder!”, but it can’t (because there is no folder called ::{...} in the current directory), so it falls back to the Documents folder.

That explains why the undefined behavior changed. But what’s the supported way of setting the initial directory to a virtual directory?

You use the IFile­Dialog interface, which gives you more control over the file open dialog than OPENFILENAME does. In particular, you can use the IFile­Dialog::Set­Default­Folder method.

Here’s a Little Program to demonstrate. Remember that Little Programs do little to no error checking. Today’s smart pointer library is (rolls dice) nothing! We’re going to use raw pointers.

#define STRICT
#define STRICT_TYPED_ITEMIDS
#include <windows.h>
#include <shlobj.h>
#include <knownfolders.h>

int WINAPI WinMain(
    HINSTANCE hinst, HINSTANCE hinstPrev,
    LPSTR lpCmdLine, int nShowCmd)
{
    CCoInitialize init;

    IFileDialog* pfd;
    CoCreateInstance(CLSID_FileOpenDialog, nullptr,
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));

    IShellItem* psi;
    SHGetKnownFolderItem(FOLDERID_RecycleBinFolder,
        KF_FLAG_DEFAULT, nullptr, IID_PPV_ARGS(&psi));
    pfd->SetDefaultFolder(psi);
    psi->Release();

    // ... other initialization goes here ...

    pfd->Show(nullptr);

    // ... process the results ...

    pfd->Release();

    return 0;
}

We create the file open dialog and set its default folder to the Recycle Bin. In a real program, there would be additional initialization of the dialog, but in this Little Program, we’ll accept all the other defaults. We then show the dialog with no owner (because our program doesn’t have a main window). Observe that the default initial directory is the Recycle Bin.

¹ The Path­Combine function has been superseded by the Path­Cch­Combine function and its friends, which support NT-style paths and extra-long paths.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.