11

我想使用 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 的跨平台解决方案。

4

4 回答 4

10

This can be complicated, for two reasons:

  1. There's a non-ASCII string in your C++ source file. How this literal gets converted to the binary representation of a const char * would depend on compiler settings and/or OS codepage settings.

  2. Windows only works with Unicode filenames through the UTF-16 encoding, while Unix uses UTF-8 for Unicode filenames.

Constructing the path object

To get this working on Windows, you can try to change your literal to wide characters (UTF-16):

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

To get a full cross-platform solution, you'll have to start with either a UTF-8 or a UTF-16 string, then make sure it gets properly converted to the path::string_type class.

Opening the file stream

Unfortunately, the C++ (and thus Boost) ofstream API does not allow specifying wchar_t strings as the filename. This is the case for both the constructor and the open method.

You could try to make sure that the path object does not get immediately converted to const char * (by using the C++11 string API) but this probably won't help:

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

For Windows to work, you might be able have to call the Unicode-aware Windows API, CreateFileW, convert the HANDLE to a FILE *, then use the FILE * for the ofstream constructor. This is all described in another StackOverflow answer, but I'm not sure if that ofstream constructor will exist on MinGW.

Unfortunately basic_ofstream doesn't seem to allow subclassing for custom basic_filebuf types, so the FILE * conversion might be the only (completely non-portable) option.

An alternative: Memory-mapped files

Instead of using file streams, you can also write to files using memory-mapped I/O. Depending on how Boost implements this (it's not part of the C++ standard library), this method could work with Windows Unicode file names.

Here's a boost example (taken from another answer) that uses a path object to open the file:

#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;
}
于 2014-05-09T01:20:27.470 回答
4

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

即使 OP确实 imbue()按照 Boost 文档中的说明(参见“Microsoft Windows 下的默认编码” )执行当前语言环境,为什么这不起作用的真正答案是因为至少有几个未解决的 boost(或 mingw)错误截至 2015 年 3 月的年数。

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

现在,为了弥补这些错误,开发人员应该做什么提升是完全不同的事情。事实证明,他们需要准确执行丹所说的内容。

于 2015-03-08T17:40:08.747 回答
2

您是否考虑过在源代码中使用 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

于 2014-05-10T04:41:40.793 回答
1

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

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

正如我所怀疑的,问题出在 line 上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。如果 Windows AreFileApisANSI() 为真,则默认灌输语言环境提供了一个 codecvt facet,它调用带有 CP_THREAD_ACP 代码页的 Windows MultiByteToWideChar 或 WideCharToMultiByte API ...

因此,Windows 中允许完整 unicode 字符集(或至少 Windows 原生提供的子集)的另一个解决方案是将文件路径指定为 aswstring而不是 as 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 还是其他东西......

于 2014-05-09T23:06:51.737 回答