【问题标题】:Read/Write file with unicode file name with plain C++/Boost使用纯 C++/Boost 读取/写入具有 unicode 文件名的文件
【发布时间】:2014-06-17 02:45:45
【问题描述】:

我想使用 boost 文件系统读取/写入具有 unicode 文件名的文件,在 Windows (mingw) 上提升语言环境(最后应该与平台无关)。

这是我的代码:

#include <boost/locale.hpp>
#define BOOST_NO_CXX11_SCOPED_ENUMS
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
namespace fs = boost::filesystem;

#include <string>
#include <iostream>

int main() {

  std::locale::global(boost::locale::generator().generate(""));
  fs::path::imbue(std::locale());

  fs::path file("äöü.txt");
  if (!fs::exists(file)) {
    std::cout << "File does not exist" << std::endl;
  }

  fs::ofstream(file, std::ios_base::app) << "Test" << std::endl;
}

fs::exists 真正检查名称为 äöü.txt 的文件。 但是写入的文件名为äöü.txt

阅读也有同样的问题。使用fs::wofstream 也无济于事,因为它只是处理宽输入。

如何使用 C++11 和 boost 解决这个问题?

编辑:错误报告发布:https://svn.boost.org/trac/boost/ticket/9968

澄清赏金:使用 Qt 非常简单,但我想要一个仅使用 C++11 和 Boost、没有 Qt 和没有 ICU 的跨平台解决方案。

【问题讨论】:

  • 实际上,给定äöü.txt,它看起来像文字已经 UTF8,除了boost::fs::path 将其视为CodePage 1252。或者更有可能,@ 987654329@ 完全忽略编码,只是传递给操作系统,操作系统假设它的代码页为 1252。
  • 重读问题,fs::exists 工作正常,这意味着错误必须在boost::fs::ofstream 中。我猜它检测到您正在使用 GCC 进行编译,因此错误地决定向操作系统传递 UTF8 编码的文件名。那将是一个提升错误。 (答案已被删除,但 OP 澄清的问题对于宽字符串文字是相同的)
  • 可能äöü不在源字符集中;尝试用等效的十六进制文字替换它们(我假设您的意思是可以存储在 8 位字符中的这些字符的版本)。
  • 但是,为什么fs::exists 有效?这似乎确实是文件系统流中的一个问题,所以我正在寻找没有它们的解决方案,或者为它们提供修复。
  • 源文件的编码可能是一切,但最有可能是UTF8。但我不明白为什么内容对文件名很重要。系统编码是用户使用的任何东西,因为我需要它独立于平台。目前我正在 Windows 上进行测试,所以 cp 1252。

标签: c++ boost unicode boost-filesystem boost-locale


【解决方案1】:

这可能很复杂,原因有两个:

  1. 您的 C++ 源文件中有一个非 ASCII 字符串。如何将此文字转换为 const char * 的二进制表示形式将取决于编译器设置和/或操作系统代码页设置。

  2. Windows 仅适用于通过 UTF-16 编码的 Unicode 文件名,而 Unix 使用 UTF-8 作为 Unicode 文件名。

构造路径对象

要使其在 Windows 上运行,您可以尝试将文字更改为宽字符 (UTF-16):

const wchar_t *name = L"\u00E4\u00F6\u00FC.txt";
fs::path file(name);

要获得完整的跨平台解决方案,您必须从 UTF-8 或 UTF-16 字符串开始,然后确保将其正确转换为 path::string_type 类。

打开文件流

不幸的是,C++(以及 Boost)ofstream API 不允许将 wchar_t 字符串指定为文件名。 constructoropen method 都是这种情况。

您可以尝试确保路径对象不会立即转换为 const char *(通过使用 C++11 字符串 API),但这可能无济于事:

std::ofstream(file.native()) << "Test" << std::endl;

要使 Windows 正常工作,您可能必须调用支持 Unicode 的 Windows API CreateFileW,将 HANDLE 转换为 FILE *,然后将 FILE * 用于 ofstream 构造函数。这都是described in another StackOverflow answer,但我不确定MinGW上是否存在ofstream构造函数。

不幸的是,basic_ofstream 似乎不允许自定义basic_filebuf 类型的子类化,因此FILE * 转换可能是唯一(完全不可移植的)选项。

另一种选择:内存映射文件

除了使用文件流,您还可以使用memory-mapped I/O 写入文件。根据 Boost 的实现方式(它不是 C++ 标准库的一部分),此方法可以用于 Windows Unicode 文件名。

这是一个使用 path 对象打开文件的 boost 示例(取自 another answer):

#include <boost/filesystem.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <iostream>

int main()
{ 
  boost::filesystem::path p(L"b.cpp");
  boost::iostreams::mapped_file file(p); // or mapped_file_source
  std::cout << file.data() << std::endl;
}

【讨论】:

  • 既然fs::exists 找到了正确的文件,那是不是意味着字符串被正确转换为path::string_type
  • 我已经更新了我的答案。看看可用的 C++ API,前景并不好......
  • 感谢您的详细解答,我需要通过它。
  • 非常感谢,映射文件可以解决问题。与直接使用文件流读写文件相比,使用它是否有缺点?
  • 缺点是您需要在打开文件之前调整文件大小(到正确的大小),并且打开文件后的任何读/写错误将在 Unix 上显示为 SIGBUS 和 @987654349 @ 在 Windows 上 - 如果未处理,则终止您的应用程序 ;)
【解决方案2】:

我不知道这里的答案是如何被接受的,因为 OP 确实 fs::path::imbue(std::locale()); 准确地 不关心操作系统的代码页,std::wstring 等等。否则,是的,他只会使用普通的旧 iconv、Winapi 调用或接受的答案中建议的其他东西。但这不是在这里使用 boost::locale 的重点。

即使 OP 确实 imbue() 当前语言环境如 Boost 文档中的指示(参见 "Default Encoding under Microsoft Windows"),这不起作用的真正答案是因为 boost(或 mingw)截至 2015 年 3 月,至少有几年仍未解决的错误。

不幸的是,mingw 用户似乎被冷落了。

现在,Boost 开发人员应该做些什么来解决这些错误是完全不同的事情。事实证明,他们需要准确执行 Dan 所说的内容。

【讨论】:

    【解决方案3】:

    您是否考虑过在源代码中使用 ASCII 字符并使用 Boost.Locale 库的 Boost Messages Formatting 功能来使用 ASCII 键查找所需字符串的方法? http://www.boost.org/doc/libs/1_55_0/libs/locale/doc/html/messages_formatting.html

    或者,您可以使用 Boost.Locale 库生成一个 UTF-8 库,然后使用“boost::path::imbue()”为 Boost.Path 注入该语言环境。 http://boost.2283326.n4.nabble.com/boost-filesystem-path-as-utf-8-td4320098.html

    这也可能对你有用。

    Microsoft Windows 下的默认编码 http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/default_encoding_under_windows.html

    【讨论】:

      【解决方案4】:

      编辑:在帖子末尾添加对 boost 和 wchar_t 的引用以及 Windows 上的另一种可能的解决方案

      我可以在 ubuntu 和 windows 上重现几乎相同的东西,甚至不用使用 boost(我的 windows 盒子上没有它)。为了解决这个问题,我只需要将源代码转换为与系统相同的编码,即 Ubuntu 上的 utf8 和 Windows 上的 latin1 或 iso-8859-1。

      正如我所怀疑的,问题来自fs::path file("äöü.txt"); 行。由于文件的编码不是预期的,它或多或少读为fs::path file("äöü.txt");。由您控制,您会发现大小为 10。这充分说明了输出文件的名称错误。

      我怀疑测试 if (!fs::exists(file)) 可以正常工作,因为 boost 或 windows 会自动修复输入的编码。

      因此,在 Windows 上,只需在代码页 1252 或 latin1 或 iso-8859-1 中使用编辑器,只要您不必使用此字符集之外的字符,就不会出现问题。如果你需要 Latin1 以外的字符,恐怕你将不得不使用 Windows 的 unicode API。

      编辑:

      事实上,Windows (> NT) 原生使用wchar_t 而不是char。毫不奇怪,windows 上的 boost 也是如此 - 请参阅 boost library filesystemreference。 提取:

      对于类似 Windows 的实现,包括 MinGW,path::value_type 是 wchar_t。默认的灌输语言环境提供了一个 codecvt facet,它 调用 Windows MultiByteToWideChar 或 WideCharToMultiByte API 如果 Windows AreFileApisANSI() 为真,则为 CP_THREAD_ACP 的代码页 ...

      因此,在 Windows 中允许完整的 unicode 字符集(或至少是 Windows 本机提供的子集)的另一个解决方案是将文件路径指定为 wstring 而不是 string。或者,如果您真的想使用 UTF8 编码的文件名,您将不得不强制线程语言环境使用 UTF8 而不是 CP1252。我不能给出代码示例,因为我的 windows 盒子上没有 boost,我的 windows 盒子运行旧 XP 并且不支持 UTF8,我不想发布未经测试的代码,但我认为在这种情况下,你应该替换

      std::locale::global(boost::locale::generator().generate(""));
      

      类似:

      std::locale::global(boost::locale::generator().generate("UTF8"));
      

      注意:未经测试,所以我不确定生成的字符串是 UTF8 还是其他的...

      【讨论】:

      • 内部字符串的大小为 10,因为该字符串包含 10 个字节长的 utf8 表示形式。对我来说不可能需要以不同的编码保存文件,因为那样我的程序就不会是跨平台的。
      • 我的意思是,在Windows下你得到一个用UTF8编码的文件名,你必须告诉系统它不是Ansi编码的,因为它是默认的。如果您需要在 Windows 和 Linux 之间移植 unicode 常量,您应该按照 Dan Cecile 的建议使用宽字符。顺便说一句,在挖掘 boost doc 之后,我认为你应该尝试在你的应用程序中使用 fs::imbue(std::locale()); 而不是 fs::path::imbue(std::locale()); 来告诉所有 boost FileSystem 模块当前的语言环境是什么——即使它应该已经是你从中获取的默认设置boost::locale::generator().generate("").
      • 设置全局语言环境和注入路径应该告诉路径对象它得到的字符串是 UTF8 编码的。 fs::exists 这样就可以正常工作。我与建议的路径一起使用的内存映射文件也找到了正确的文件。所以,我认为没有宽字符是可能的。
      猜你喜欢
      • 2021-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多