【问题标题】:Open utf8 encoded filename in c++ Windows在 C++ Windows 中打开 utf8 编码的文件名
【发布时间】:2015-08-30 00:22:44
【问题描述】:

考虑以下代码:

#include <iostream>
#include <boost\locale.hpp>
#include <Windows.h>
#include <fstream>

std::string ToUtf8(std::wstring str)
{
    std::string ret;
    int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0, NULL, NULL);
    if (len > 0)
    {
        ret.resize(len);
        WideCharToMultiByte(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len, NULL, NULL);
    }
    return ret;
}

int main()
{
    std::wstring wfilename = L"D://Private//Test//एउटा फोल्दर//भित्रको फाईल.txt";
    std::string utf8path = ToUtf8(wfilename );
    std::ifstream iFileStream(utf8path , std::ifstream::in | std::ifstream::binary);
    if(iFileStream.is_open())
    {
        std::cout << "Opened the File\n";
        //Do the work here.
    }
    else
    {
        std::cout << "Cannot Opened the file\n";

    }
    return 0;

}

如果我正在运行该文件,我将无法打开该文件,从而进入else 块。即使使用boost::locale::conv::from_utf(utf8path ,"utf_8") 而不是utf8path 也不起作用。如果我考虑使用wifstream 并使用wfilename 作为其参数,则该代码有效,但我不想使用wifstream。有什么方法可以打开名称为utf8 编码的文件?我正在使用Visual Studio 2010

【问题讨论】:

  • 没有任何底层 Windows API 使用 UTF8。 std::ifstream 最终会调用 CreateFileA 或 CreateFileW 来打开文件,这些函数都不是 UTF8。
  • 所以如果我要使用ifstream 我应该如何更改代码以使其工作。我应该使用wstring
  • 问题是我正在尝试使代码跨平台。由于 Linux 已经支持 unicode,如果我使用ifstream,代码应该可以工作。我该如何应对这种情况?
  • 这取决于您的标准库实现。我熟悉的一个,实际上是不可能的,您不能将 iostreams 用于可能具有非 8 位文件名的文件。
  • 所以我唯一的选择是使用ifdefs 并使用wstring 用于Windows 和string 用于Linux 操作系统吗?还有其他方法吗?

标签: c++ windows


【解决方案1】:

在 Windows 上,您必须使用 8 位 ANSI(并且它必须与用户的语言环境匹配)或 UTF-16 作为文件名,没有其他选项可用。您可以在主代码中继续使用 string 和 UTF-8,但在打开文件时必须将 UTF-8 文件名转换为 UTF-16。效率较低,但这是您需要做的。

幸运的是,VC++ 对std::ifstreamstd::ofstream 的实现具有非标准 的构造函数重载和open() 方法来接受wchar_t* UTF-16 文件名字符串。

explicit basic_ifstream(
    const wchar_t *_Filename,
    ios_base::openmode _Mode = ios_base::in,
    int _Prot = (int)ios_base::_Openprot
);

void open(
    const wchar_t *_Filename,
    ios_base::openmode _Mode = ios_base::in,
    int _Prot = (int)ios_base::_Openprot
);
void open(
    const wchar_t *_Filename,
    ios_base::openmode _Mode
);
explicit basic_ofstream(
    const wchar_t *_Filename,
    ios_base::openmode _Mode = ios_base::out,
    int _Prot = (int)ios_base::_Openprot
);

void open(
    const wchar_t *_Filename,
    ios_base::openmode _Mode = ios_base::out,
    int _Prot = (int)ios_base::_Openprot
);
void open(
    const wchar_t *_Filename,
    ios_base::openmode _Mode
);

您将不得不使用#ifdef 来检测 Windows 编译(不幸的是,不同的 C++ 编译器识别不同)并在打开文件时临时将您的 UTF-8 字符串转换为 UTF-16。

#ifdef _MSC_VER
std::wstring ToUtf16(std::string str)
{
    std::wstring ret;
    int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0);
    if (len > 0)
    {
        ret.resize(len);
        MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len);
    }
    return ret;
}
#endif

int main()
{
    std::string utf8path = ...;
    std::ifstream iFileStream(
        #ifdef _MSC_VER
        ToUtf16(utf8path).c_str()
        #else
        utf8path.c_str()
        #endif
        , std::ifstream::in | std::ifstream::binary);
    ...
    return 0;
}

请注意,这仅保证在 VC++ 中有效。不保证其他适用于 Windows 的 C++ 编译器提供类似的扩展。

更新:从 Windows 10 Insider Preview Build 17035 开始,Microsoft 现在支持 UTF-8 作为系统范围的编码,用户可以将其区域设置为。从 Windows 10 版本 1903(内部版本 18362)开始,应用程序现在可以通过其应用程序清单选择使用 UTF-8 作为进程范围的代码页,即使用户区域设置未设置为 UTF-8。这些功能允许基于 ANSI 的 API(如 CreateFileA()std::ifstream/std::ofstream 在内部使用)处理 UTF-8 字符串。因此,理论上,启用此功能后,您可能能够将 UTF-8 编码的字符串传递给 std::ifstream/std::ofstream,它会“只是工作”。我无法确认,因为这在很大程度上取决于实施。坚持传递 UTF-16 文件名会更安全,因为这是 Windows 的本机编码,ANSI API 将简单地在内部转换。

【讨论】:

  • +1 这行得通。对于那些想要将utf8 转换为utf16 的人,还有另一个函数here 可用。
  • 有很多 UTF 转换实现可用。手动实现(如您链接的那个)、Unicode 库(如 libiconv 和 ICU),甚至 C++11 中的 std::codecvt_utf8_utf16
  • 您可以创建一个函数filename(const std::string &amp;fname) 并将所有令人讨厌的东西放在一个地方,而不是把#ifdef 放在每个打开的文件中。然后,您只需在文件名上使用该函数即可打开文件。
  • @Raedwald 不,我的意思是 8 位 ANSI。未以 UTF 编码的 Unicode 字符串需要 8 位编码,例如 Windows-1252 等(7 位 ASCII 是 UTF-8 的子集)。在 Windows 上,用户区域设置是使用实现这些编码的code pages 实现的。因此,Windows 系统上的文件名必须以 UTF-16 或用户的默认 ANSI 代码页编码。
  • @jpo38 你可以使用任何你想实现的东西ToUtf16()。大量的 Unicode API 可供选择。 wstring_convert() 可以使用,但请注意它在 C++17 中已被弃用,尚未定义标准替换。
【解决方案2】:

你可以在 C++14/17 中使用std::filesystem::u8path

std::filesystem::path pa = std::filesystem::u8path((const char*)yourStdStringPath.c_str());
std::ofstream ofs(pa);

它在 C++20 中已被弃用,因为您可以使用 u8 前缀。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-04
    • 2014-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    相关资源
    最近更新 更多