How to prevent GetOpenFileName from changing the current directory while the dialog is shown?

为君一笑 提交于 2021-02-20 04:26:06

问题


GetOpenFileName (for questionable reasons) changes the current directory of an application while the dialog is shown. This can be reset on dialog closure by specifying OFN_NOCHANGEDIR as dialog initialization flag:

OFN_NOCHANGEDIR Restores the current directory to its original value if the user changed the directory while searching for files.

Setting this flag, however, doesn't prevent the function from changing the current directory while the explorer dialog is shown.

This is an issue in multithreaded environments where other threads rely on the current directory to remain being the executable's path.

Is there a way to prevent GetOpenFileName from changing the current directory of an application while the explorer dialog is shown and the user browses through folders?


回答1:


You have several options here:

  1. Use detours or MinHook to hook SetCurrentDirectory and make it do nothing for your process (ugly)
  2. Use a custom file chooser that does not change current directory. (better)
  3. Remove dependencies on current directory in your code, as you're likely to run into other bugs related to it, especially in multi-threaded environment. (best)



回答2:


I decided to post what ought to be a workable solution to this. It involves fixing the code 'properly' to use absolute paths but bear with me. It's not that hard. The answer posted by the OP is just oh so dangerous. It's not 'cheap' at all. On the contrary, it is likely to prove very expensive in the long run.

Now I assume that there are function / method calls scattered throughout the code that access files or folders via relative pathnames (most likely unqualified filenames). There may well be a lot of these but they probably all boil down to this:

do_something_with_this_file ("somefile.foo");

Now if do_something_with_this_file is a function defined within the application the solution is obvious: change the implementation of that function to convert the parameter passed in to an absolute path name, if necessary, before doing anything with it.

But life is not so easy if do_something_with_this_file is something like a call to CreateFile or perhaps fopen. Then we are stuck with the existing behaviour for that function. Or are we? Actually, no. There's a simple solution involving just a macro and a small amount of implementation work.

Time for an example. I will use fopen since it has a nice simple API, but it applies equally well to CreateFile or indeed anything else.

Firstly, create a header file, lets call it AbsolutePathHelpers.h, which redefines fopen, and whatever else you need, like so:

// AbsolutePathHelpers.h

FILE *APHfopen (const char *filename, const char *mode);
#define fopen APHfopen
// ...

Make sure that this header file gets included in all your compilation units, probably by #includeing it in some common header file that they all use.

Now write the implementation of APHfopen in a new .cpp file (let's call it AbsolutePathHelpers.cpp) which you will need link in with your project:

// AbsolutePathHelpers.cpp
#include <stdio.h>

#undef fopen
FILE *APHfopen (const char *filename, const char *mode)
{
    printf ("Opening %s\n", filename);      // diagnostic message for testing
    // Convert filename to absolute path here (if necessary) before calling the 'real' fopen
    return fopen (filename, mode);
}

// ...

And finally a simple test program:

// Test program
#include <stdio.h>

int main ()
{
    FILE *f = fopen ("myfile", "r");
    if (f)
        fclose (f);

    printf ("Finished.  Press any key...");
    getchar ();
    return 0;
}

Output:

Opening myfile
Finished.  Press any key...

Run it at Wandbox.

I'll leave the details of converting relative paths to absolute paths to the OP. All you need is a global variable containing the current directory in effect when the program starts up. Or, I strongly suspect, that should actually be the directory containing the executable.




回答3:


As others in the comments mentioned, it's not ideal to rely on the current directory to remain the same at all times. I would've changed it if it didn't require rewriting a resource loading factory (not my code).

However, one, admittedly cheap and messy, solution for this problem is to hook the window procedure of the OpenFileDialog and react to the folder change notification. From there, a previously backed up directory path can be "forced" as the current directory. This is what I did:

Backup:

// Well outside of the OpenFileDialog logic
static TCHAR g_BackupDir[MAX_PATH];
...
GetCurrentDirectory(ARRAYSIZE(g_BackupDir), g_BackupDir);

Hook:

// Hooking procedure specified as lpfnHook parameter to the GetOpenFileName function
// Requires the flags OFN_EXPLORER and OFN_ENABLEHOOK to be set as well
UINT_PTR CALLBACK OFNHookProc(_In_ HWND hdlg, _In_ UINT uiMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
    switch (uiMsg)
    {
        case WM_NOTIFY:
        {
            LPNMHDR pNotify = reinterpret_cast<LPNMHDR>(lParam);
            switch (pNotify->code)
            {
                case CDN_FOLDERCHANGE:
                    // Force back initial current dir
                    SetCurrentDirectory(g_BackupDir);

                    // No further processing on dialog
                    return 1;
            }
        }
        break;
    }

    // Default processing
    return 0;
}

The dialog logic itself doesn't seem to depend on the current directory in any way, shape or form. It works perfectly even when the current directory is being forced back on folder changes.

Goes into the category "Does the trick" and inferior to using absolute paths.



来源:https://stackoverflow.com/questions/50468051/how-to-prevent-getopenfilename-from-changing-the-current-directory-while-the-dia

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!