"C.UTF-8" Windows 上的 C++ 语言环境?

"C.UTF-8" C++ locale on Windows?

我正在修复一个大型开源跨平台应用程序,以便它可以处理 Windows.

上包含非 ANSI 字符的文件路径

更新:

根据我目前得到的答案和评论(谢谢!)我觉得我应该澄清一些要点:

  1. 不能修改几十个第三方库的代码才能使用std::wchar_t。这不是一个选择。该解决方案必须使用普通的 ol' std::fopen()std::ifstream

  2. 我在下面概述的解决方案工作在 99%,至少在我正在开发的系统上(Windows 10 版本 1909,构建 18363.535)。我还没有在任何其他系统上测试过。

    唯一剩下的问题,至少在我的系统上,基本上是数字格式,我希望替换 std::numpunct facet 可以解决问题(但我还没有成功)。


我目前的解决方案包括:

  1. 将 Windows 上的 LC_CTYPE 类别的 C 语言环境设置为 .UTF-8(所有其他类别均根据需要设置为 C 语言环境通过应用程序):

    // Required by the application.
    std::setlocale(LC_ALL, "C");
    
    // On Windows, we want std::fopen() and other functions dealing with strings
    // and file paths to accept narrow-character strings encoded in UTF-8.
    #ifdef _WIN32
    {
    #ifndef NDEBUG
        char* new_ctype_locale =
    #endif
            std::setlocale(LC_CTYPE, ".UTF-8");
        assert(new_ctype_locale != nullptr);
    }
    #endif
    
  2. 配置 boost::filesystem::path 以使用 en_US.UTF-8 语言环境,这样它也可以处理包含非 ANSI 字符的路径:

    boost::filesystem::path::imbue(std::locale("en_US.UTF-8"));
    

最后遗漏的一点是使用 C++ 流修复文件 I/O,例如

std::ifstream istream(filename);

最简单的解决方案可能是在应用程序开始时设置全局 C++ 语言环境:

std::locale::global(std::locale("en_US.UTF-8"));

然而,这会弄乱数字的格式,例如1234.56 被格式化为 1,234.56.

是否有语言环境 只是 将编码指定为 UTF-8 而不会混淆数字格式(或其他内容)?

基本上我正在寻找 C.UTF-8 语言环境,但在 Windows 上似乎不存在。

更新:我认为一种解决方案是重置区域设置的某些(大部分?全部?)方面,但我很难找到有关如何执行此操作的信息。

别管语言环境。

在 Windows 上,您应该使用 Microsoft's extension 添加构造函数,将 const std::wchar_t*(预期指向 UTF-16)添加到 std::ifstream

希望您所有的字符串都是 UTF-8,或者一些一致且合理的编码。

所以只需获取 UTF-8 → UTF-16 转换器(它们是轻量级的)并将文件名作为 UTF-16 传递给 std::ifstream(在 std::wchar_t* 中)。

(一定要 #ifdef 出来,这样就不会在任何其他平台上尝试。)

出于同样的原因,您也应该以同样的方式使用 _wfopen 而不是 std::fopen

就是这样。

Windows API 不尊重 CRT 语言环境,fopen 等的 CRT 实现直接调用 narrow-char API,因此更改语言环境不会影响编码。

但是,Windows 2019 年 5 月 10 日更新(版本 1903)introduced a support for UTF-8 in its narrow-char APIs。它可以通过将适当的清单嵌入到您的可执行文件中来启用。不幸的是,这是最近才添加的,因此如果您需要针对较旧的系统,则可能不是一个选项。

您的其他选择包括手动转换为 wchar_t 或使用为您转换的层(例如 Boost.Filesystem,甚至更好,Boost.Nowide)。