如何在显示对话框时阻止GetOpenFileName更改当前目录?

时间:2018-05-22 12:43:42

标签: c++ c winapi openfiledialog getopenfilename

GetOpenFileName(出于可疑原因)在显示对话框时更改应用程序的当前目录。通过将OFN_NOCHANGEDIR指定为对话框初始化标志:

,可以在对话框关闭时重置此项
  

OFN_NOCHANGEDIR 如果用户在搜索文件时更改了目录,则将当前目录恢复为原始值。

但是,设置此标志不会阻止函数在显示资源管理器对话框时更改当前目录

这是多线程环境中的一个问题,其他线程依赖当前目录保持可执行文件的路径。

有没有办法阻止GetOpenFileName在显示资源管理器对话框并且用户浏览文件夹时更改应用程序的当前目录?

3 个答案:

答案 0 :(得分:2)

这里有几个选项:

  1. 使用绕道或MinHook挂钩SetCurrentDirectory并使其不为您的进程做任何事(丑陋)
  2. 使用不更改当前目录的custom file chooser。 (优于)
  3. 删除代码中当前目录的依赖项,因为您可能遇到与其相关的其他错误,尤其是在多线程环境中。 (最好)

答案 1 :(得分:0)

我决定发布应该是一个可行的解决方案。它涉及“正确”修复代码以使用绝对路径,但请耐心等待。这并不难。 OP发布的答案非常危险。它根本不“便宜”。相反,从长远来看,这可能会非常昂贵。

现在我假设在整个代码中分散了函数/方法调用,它们通过相对路径名(很可能是不合格的文件名)访问文件或文件夹。可能有很多这些,但它们可能都归结为:

do_something_with_this_file ("somefile.foo");

现在,如果do_something_with_this_file是在应用程序中定义的函数,则解决方案是显而易见的:更改该函数的实现,以便在执行任何操作之前将传入的参数转换为绝对路径名。 / p>

但如果do_something_with_this_file类似于对CreateFilefopen的调用,那么生活就不那么容易了。然后我们坚持使用该函数的现有行为。或者是我们?实际上,没有。有一个简单的解决方案只涉及宏和少量的实现工作。

时间示例。我将使用fopen,因为它有一个很好的简单API,但它同样适用于CreateFile或其他任何东西。

首先,创建一个头文件,让我们调用它AbsolutePathHelpers.h,重新定义fopen,以及您需要的任何其他内容,如下所示:

// AbsolutePathHelpers.h

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

确保此头文件包含在所有编译单元中,可能是#include将它们放在一些常用的头文件中。

现在将APHfopen的实现写入一个新的.cpp文件(让我们称之为AbsolutePathHelpers.cpp),您需要将其与项目链接:

// 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);
}

// ...

最后是一个简单的测试程序:

// Test program
#include <stdio.h>

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

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

输出:

Opening myfile
Finished.  Press any key...

Wandbox运行。

我将保留将相对路径转换为OP的绝对路径的详细信息。您只需要一个全局变量,其中包含程序启动时生效的当前目录。或者,我强烈怀疑,它实际上应该是包含可执行文件的目录。

答案 2 :(得分:-1)

正如提到的评论中的其他人一样,依赖当前目录始终保持不变是不理想的。如果它不需要重写资源加载工厂(而不是我的代码),我会改变它。

但是,对于这个问题,一个公认的廉价和凌乱的解决方案是挂钩OpenFileDialog的窗口过程并对文件夹更改通知做出反应。从那里,以前备份的目录路径可以“强制”为当前目录。这就是我所做的:

备份:

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

钩:

// 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;
}

对话逻辑本身似乎不以任何方式,形状或形式依赖于当前目录。即使当前目录被强制恢复文件夹更改时,它也能正常工作。

进入类别“诀窍”并且不如使用绝对路径。