【问题标题】:OPENFILENAME dialog returns Asian letters instead of file pathOPENFILENAME 对话框返回亚洲字母而不是文件路径
【发布时间】:2016-11-27 10:41:42
【问题描述】:

我正在尝试创建一个从OPENFILENAME 对话框获取文件路径的函数。我的代码看起来像这样。

wstring src;

bool open()
{
    const string title  = "Select a File";

    wchar_t filename[MAX_PATH];

    OPENFILENAMEA ofn;
    ZeroMemory(&filename, sizeof(filename));
    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = NULL; 
    ofn.lpstrFilter     = "Music (.mp3)\0*.mp3\0All\0*.*\0";
    ofn.lpstrFile       = LPSTR(filename);
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrTitle      = title.c_str();
    ofn.Flags           = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;

    if (GetOpenFileNameA(&ofn))
    {
        src = filename;    //<----------Save filepath in global variable
        return true;
    }
    return false;
}

在注释行放置断点后,我可以检查“src”和“文件名”的值,在这一点上,对我来说,它们是无法识别的亚洲字母。为什么会这样?这是转换问题吗?

编辑:

感谢快速回复和一些 cmets,代码现在可以正常运行。感谢 Hans Passant 提供了一个非常直接的解决方案,也非常感谢 Cody Gray 重写了函数,解释了错误,并给了我如何处理它的教训。由于我仍在迈出学习 winapi 的第一步,因此这些信息将在以后的课程中为我提供很好的帮助。

【问题讨论】:

  • 尝试char filename[MAX_PATH]; 而不是 wchar_t
  • LPSTR 演员阵容非常邪恶,它阻止了编译器告诉你你做错了。然而并没有阻止你做错事。请改用 OPENFILENAMEW 和 GetOpenFileNameW,使用 L"blabla" 生成宽字符串文字。
  • 'GetOpenFileNameA' 是 'GetOpenFileName' 的 ANSI 子函数,因此您应该检查您的代码是否构建为 Unicode。并且你应该使用'GetOpenFileName'而不是'GetOpenFileNameA',好的子功能的选择会自动完成。

标签: c++ getopenfilename


【解决方案1】:

这是一个由 ANSI 和 Unicode 字符类型混合引起的经典错误。它的标志是字符串中随机出现的亚洲起源字符,正如您所描述的那样。

快速浏览一下您的代码会立即发现问题所在。您正在调用 ANSI 版本的 Win32 函数 —GetOpenFileNameA — 并使用 ANSI 版本的数据结构 —OPENFILENAMEA — 但您的 filename 数组由宽字符 (wchar_t) 组成。在 Win32 中,以A 为后缀的类型始终是 ANSI,并且需要使用 1 字节的 char 类型。 W-suffixed 类型始终是 Unicode,并且需要使用 2 字节 wchar_t 类型。您不能在没有明确转换的情况下将两者混合使用,例如使用MultiByteToWideChar 和/或WideCharToMultiByte

请注意,如果您没有使用显式强制转换将其关闭,编译器会捕获此类型不匹配错误。

在现代,您应该始终使用W-suffixed API,因为您始终希望支持 Unicode。世界不是 ASCII,当每个人都切换到 Windows NT 和 Windows 98/ME 时,Windows ANSI 编码已经过时了。

所以重写你的代码如下(我还冒昧地改变了一些其他的习惯用法并以其他方式清理代码,比如使用 C++ 语言结构来零内存):

std::wstring src;

bool open()
{
    const std::wstring title = L"Select a File";
    std::wstring filename(MAX_PATH, L'\0');

    OPENFILENAMEW ofn = { };
    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = NULL; 
    ofn.lpstrFilter     = L"Music (.mp3)\0*.mp3\0All\0*.*\0";
    ofn.lpstrFile       = &filename[0];  // use the std::wstring buffer directly
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrTitle      = title.c_str();
    ofn.Flags           = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;

    if (GetOpenFileNameW(&ofn))
    {
        src = filename;    //<----------Save filepath in global variable
        return true;
    }
    return false;
}

为避免每次都显式键入W,请确保为您的项目全局定义UNICODE_UNICODE。最好的方法是使用项目属性,您可以在其中预定义这些符号。否则,您可以在预编译头文件的顶部定义它们。这可确保始终使用带有W-suffixed 的函数和类型变体,即使您省略了后缀。所以你可以简单地说GetOpenFileNameOPENFILENAME。 Windows 头文件中的宏处理分辨率。

这样做的好处是,如果您忘记在宽字符串文字前加上 L 前缀,它会导致编译器生成类型不匹配错误,如果您还没有习惯,这是一个常见的错误这样做。 (当然,这假设您也改掉了使用显式强制转换来消除编译器警告的坏习惯,因为这样做很容易破坏这个安全网。)

【讨论】:

  • 非常翔实的解释了错误是如何产生的,如何解决问题,并指出为什么这个解决方案比其他解决方案更重要。非常感谢。
猜你喜欢
  • 2022-06-17
  • 2013-05-23
  • 2018-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多