【问题标题】:How to prevent GetOpenFileName from changing the current directory while the dialog is shown?如何防止 GetOpenFileName 在显示对话框时更改当前目录?
【发布时间】:2018-05-22 12:43:42
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 没有帮助,但“其他线程依赖当前目录保留的位置”不是(线程)问题吗?你不能让其他线程不需要这个吗? (相关entertainment
  • 看来这似乎无法避免,您唯一能做的就是让所有线程在当前目录中使用一个变量,并使所有文件访问都使用该变量进行绝对访问(该变量可以是一个参数,可以是线程全局的,也可以是全局的,如果所有线程都使用相同的当前目录)。
  • 当你说“这是多线程环境中的一个问题,其他线程依赖当前目录作为可执行文件的路径”,他们永远不能依赖它。无论进程启动时发生了什么,都没有任何保证。所以我支持 Christan:如果可以,请修复应用程序。
  • @Christian.K 当一个应该显示文件夹内容的函数决定更改整个应用程序的当前目录时,我不明白这是一个线程问题。而且恐怕在资源工厂建立在当前目录逻辑(不是我写的)上的游戏上并不是那么容易。 “当前目录地狱”是我当然可以联系到的。
  • 可以说,real 错误依赖于当前工作目录。它是一个进程范围的属性,任何线程都可以随时更改它。您无法可靠地控制多线程应用程序中的当前工作目录。始终构造完全限定的路径名​​。您可以调用GetModuleFileName 或朋友进入可执行文件的目录。

标签: c++ c winapi openfiledialog getopenfilename


【解决方案1】:

你有几个选择:

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

【讨论】:

  • 我怀疑这个问题是否有一个干净的解决方案。正如其他人所提到的,理想情况下,我将不得不重写大量代码库,以确保文件操作不直接依赖于当前目录。谢谢你的鼓励。当然,当然不止这 3 个选项。我已经发布了另一个我坚持作为答案的解决方案。 @PaulSanders 提到了一个辅助进程。所以还有两个。
【解决方案2】:

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

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

do_something_with_this_file ("somefile.foo");

现在,如果do_something_with_this_file 是应用程序中定义的函数,则解决方案很明显:更改该函数的实现,将传入的参数转换为绝对路径名(如有必要),然后再对其进行任何操作。

但是,如果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。您所需要的只是一个包含程序启动时生效的当前目录的全局变量。或者,我强烈怀疑,这实际上应该是包含可执行文件的目录。

【讨论】:

    【解决方案3】:

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

    然而,一个公认的廉价和混乱的解决方案是挂钩 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;
    }
    

    对话逻辑本身似乎不以任何方式、形状或形式依赖于当前目录。即使当前目录被强制返回文件夹更改,它也能完美运行。

    进入“有效”类别,不如使用绝对路径。

    【讨论】:

    • 我考虑过在我的答案中发布此选项,但考虑到您说您在多线程环境中工作,我认为这不是一个好选项。这将为另一个线程在调用 SetCurrentDirectory 并将其设置回之前调用 GetCurrentDirectory 留下一个很小的机会,因此可能会导致那些难以跟踪的错误。但是,如果它适合你...
    • 是的,好地方!在文件夹更改的同一堆栈调用上调用钩子 proc,它几乎在有问题的目录更改后立即被调用。所以我认为这几乎可以一直工作,并且考虑到这种情况,这对我来说是一个足够好的解决方案。谢谢:)
    • “几乎一直”真的足够好吗?如果这导致您的最终产品中出现零星的、不可预测的和微妙的错误,您可能会后悔。我个人永远不会考虑它。妥善解决问题。人们已经发布了至少两种可能的方法,但您似乎一心只想“按自己的方式”做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 2015-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多