【问题标题】:Cross-platform file names and wxString::ToStdString()跨平台文件名和 wxString::ToStdString()
【发布时间】:2021-08-14 22:08:15
【问题描述】:

我想在使用 wxWidgets 3.1 的跨平台应用程序中处理文件。我依赖一些只接受文件名作为std::string 的函数。

在 Windows 上,我可以简单地使用 wxString::ToStdString(),一切都很好。

在 Linux (Ubuntu 20.04 LTS) 上,当文件名或路径中包含“特殊”字符(例如,法语 Ubuntu“Téléchargement”上的默认下载目录)时,转换失败并返回一个空字符串。

当我在 Linux 上明确指定以下转换器时,转换成功:std::string str = wxs.ToStdString(wxMBConvUTF8());

但这在 Windows 上不起作用,并且会打乱“特殊”字符。

我想,我可以编写依赖于平台的代码来处理这个问题,但这违背了工具包的目的。

我对此做了很多研究,但我现在完全糊涂了。我认为wxString 在 Unicode wxWidgets 构建(我正在使用)中使用 std::string?为什么这(显然)依赖于平台?我错过了什么?

这是一个最小的例子,它会弹出三个消息框:第一个正确显示wxString,第二个显示没有字符串(因为转换失败),第三个显示转换完成后的字符串.在 Windows 上,前两个框正确显示字符串,最后一个框显示两个 'é' 的错误字符。

#include "wx/wx.h"
#include <fstream>

class MyApp : public wxApp
{
public:
    virtual bool OnInit() wxOVERRIDE;
};

class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString& title);

private:
};
wxIMPLEMENT_APP(MyApp);


bool MyApp::OnInit()
{
    if (!wxApp::OnInit())
        return false;

    // create the main application window
    MyFrame* frame = new MyFrame("Minimal wxWidgets App");
    frame->Show(true);
    return true;
}


// Some file I/O function
std::string openFile(std::string fileName)
{
    std::ifstream file(fileName);
    if (file)
    {
        return "Success!";
    }
    else
    {
        return "Failure!";
    }

}

// frame constructor
MyFrame::MyFrame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    wxString name = wxFileSelector("Pick a file");  // Pick a file with a "special" character in the name, e.g. Äréa.txt

    wxMessageBox(openFile(name.ToStdString()));  // Success! on Windows; Failure! on Linux

    wxMessageBox(openFile(std::string(name.ToUTF8())));  // Failure! on Windows; Success! on Linux
}

编辑: 我发现 wxWidgets(在 3.1.5 中)最近添加了这个:

Add wxString::utf8_string() 这又增加了一个转换 函数,虽然不理想,但还是比不得不写的好 ToStdString(wxConvUTF8) 每次无损转换 wxString to std::string: 这不仅太长,而且太容易 忘记指定wxConvUTF8,导致使用时数据丢失 非 UTF-8 语言环境。

所以你会认为这可以解决问题。但是我 am 使用的是 UTF-8 语言环境!这是locale 命令的输出:

LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=de_DE.UTF-8
LC_TIME=de_DE.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=de_DE.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=de_DE.UTF-8
LC_NAME=de_DE.UTF-8
LC_ADDRESS=de_DE.UTF-8
LC_TELEPHONE=de_DE.UTF-8
LC_MEASUREMENT=de_DE.UTF-8
LC_IDENTIFICATION=de_DE.UTF-8
LC_ALL=

EDIT2:我将最小示例更改为更接近我实际尝试做的事情。

EDIT3:我将示例更改为甚至更接近我实际尝试做的事情。 :-)

【问题讨论】:

  • 可以改一下函数实现吗?我可能会尝试使用 std::wstring 来代替......如果做不到这一点 - 我可能会编写依赖于平台的代码。您所依赖的函数不支持 UNICODE。
  • 但是std::string可以处理UTF-8,这里应该够用了。我认为这就是它在 Windows 上运行的原因。为什么它不能在 Linux 上运行?
  • 我也认为std::string str = wxs.utf8_str(); 应该适用于所有平台。你能看看这是否有效吗?
  • @Simon,你确定是 UTF-8 吗?记住 Windows 原生字符串是 UTF-16...
  • 确实如此,但 STL 字符串仍然是 UTF-8 并且应该是可移植的:docs.microsoft.com/en-us/archive/msdn-magazine/2016/september/…

标签: c++ unicode wxwidgets stdstring


【解决方案1】:

“接受文件名为std::string 的函数”必须以UTF-8 格式接受它们,因此您需要使用wxString::utf8_str() 或其同义词ToUTF8(),而不是像当前那样依赖当前的语言环境编码。在任何情况下,您仍然需要将结果传递给 std::string ctor,因为这些函数会返回 char* 指针或缓冲区。

在最新的 3.1 版本的 wxWidgets 中,您还拥有wxString::utf8_string(),它直接返回一个 UTF-8 编码的std::string,因此使用起来更加方便。

【讨论】:

  • 调用 ToUTF8() 在 Linux 上有效,但在 Windows 上似乎无效。 “特殊”字符是乱码。但是,ToStdString() 返回正确的表示。但我不应该为此编写依赖于平台的代码,对吧?
  • ToUTF8() 绝对有效。如果你得到意外的输出,这意味着当你调用ToUTF8()时你的字符串已经包含垃圾,即你也可能错误地填充它(特别是,如果你从UTF-8编码的字符串创建它,你最好也使用FromUTF8(),而不是依赖当前的编码是UTF-8,这在Windows下是永远不会出现的)。
  • 很抱歉,我无法让它工作。请参阅我上面帖子中的最小示例。
  • 我认为您对auto 的使用感到困惑。您的utfStrstd::string,因此当您将其传递给wxMessageBox() 时,您需要指定其编码,即使用wxMessageBox(wxString::FromUTF8(utfStr)),否则您将再次使用当前语言环境编码而不是UTF-8。同样,规则很简单:默认情况下,所有char* 字符串都使用当前的语言环境编码,对于 UTF-8 编码的字符串,您必须明确使用{To,From}UTF8()
  • 很抱歉,如果我很迟钝,或者我通过更改示例来传播混乱,但我仍然不明白。我现在明白我需要使用{To,From}UTF8() 在UTF8 编码的std::stringwxString 之间来回转换。但是讨论偏离了我真正想知道的内容(我确信这是因为我提出示例的方式令人困惑):Why do I need platform-dependent conversions when handling unwrapped file I/O functions (see the updated例子)?
【解决方案2】:

显然函数wxString::ToStdString()使用了程序当前语言环境的编码。

程序的默认语言环境不是用户环境中设置的语言环境。所有 C 和 C++ 程序都以名为“C”的语言环境开始。为了使用用户环境中指定的语言环境,需要调用

setlocale(LC_ALL, "");

在程序的开头。 C++ 有它自己的方法来做到这一点

 std::locale::global(std::locale(""));

但有时还是不行,大概是因为C++标准库实现的bug,所以C方式比较靠谱。

如果您的框架有自己的处理语言环境的方法,您可能应该改用它。

【讨论】:

  • 我意识到这是可行的,但有一些副作用,可能会带来其他问题。例如,更改区域设置也可能会更改小数点符号。
  • 是的,它会将小数点设置为用户想要的。
  • 这听起来不错,但是当您的程序与某种包含十进制数字的文本文件(例如 XML)交互时,解析会变得更加复杂。
  • 每个流都可以注入自己的语言环境,也可以研究语言环境方面。
  • 我确实最终使用了这个解决方案,但稍作修改,只更改了编码:setlocale(LC_ALL, "C.UTF-8");
【解决方案3】:

您应该使用wxString::fn_str(),它以合适的类型返回合适的字符串以用作文件名。

如果您在 Windows 上使用 wxString::ToStdString(),则一切都不再“正常”,例如字符串中的西里尔字母,您的语言环境是德语。转换失败。

【讨论】:

  • 感谢您为我指明方向!不过,我认为它不能真正解决我的问题有两个原因:该函数不返回std::string,因此我还需要将其结果传递给std::string 构造函数。第二个原因是问题不仅延伸到文件名,尽管那是让我意识到有问题的触发器。但这绝对可以让我写一个解决方法,如果没有更好的答案出现,我会接受这个。
  • 另外,将wxString::fn_str() 的结果传递给std::string 构造函数似乎只适用于Linux,但不适用于Windows。
猜你喜欢
  • 2014-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 2011-02-20
  • 2014-10-15
  • 1970-01-01
  • 2010-12-06
相关资源
最近更新 更多