用C++在Windows中打开UTF8编码的文件名

18

请考虑以下代码:

#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


3
Windows API中没有使用UTF8编码。std::ifstream最终会调用CreateFileA或CreateFileW打开文件,但这两个函数都不支持UTF8编码。我会尽力让翻译更易读懂,但不会改变原意,也不会提供任何解释。 - Richard Critten
如果我要使用 ifstream,我应该如何更改代码使其正常工作?我应该使用 wstring 吗? - Mahadeva
问题是我正在尝试使代码跨平台。由于Linux已经支持Unicode,如果我使用ifstream,代码应该可以正常工作。我应该如何解决这个问题? - Mahadeva
这取决于您的标准库实现。我熟悉的一个实现中,实际上是不可能的,您无法在可能具有非8位文件名的文件中使用iostreams。 - M.M
那么我的唯一选择是使用#ifdef并在Windows中使用wstring,在Linux OS中使用string吗?还有其他的方法吗? - Mahadeva
2个回答

33

在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预览版Build 17035起,Microsoft现在支持UTF-8作为用户可以设置其区域设置的系统范围内编码。从Windows 10版本1903(build 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 的人,还有另一个函数可用,可以在这里找到。 - Mahadeva
3
有许多 UTF 转换实现可用。手动实现(如您提供的链接),Unicode 库(例如 libiconv 和 ICU),甚至 C++11 中的 std::codecvt_utf8_utf16 都可以使用。 - Remy Lebeau
不要在每个文件打开时都放置 #ifdef,你可以创建一个函数 filename(const std::string &fname) 并将所有糟糕的东西放在一个地方。然后你只需要在需要打开文件的地方使用该函数即可。 - Mark Ransom
2
@Raedwald 不,我真的是指8位ANSI。未在UTF中编码的Unicode字符串需要使用8位编码,例如Windows-1252等(7位ASCII是UTF-8的子集)。在Windows上,用户区域设置使用实现这些编码的代码页来实现。因此,在Windows系统上的文件名必须以UTF-16或用户的默认ANSI代码页进行编码。 - Remy Lebeau
1
@jpo38,你可以使用任何你想要的方法来实现 ToUtf16()。有很多Unicode API可供选择。wstring_convert() 可以工作,但请注意它在C++17中已被弃用,目前没有定义标准替代方案。 - Remy Lebeau
显示剩余7条评论

3
你可以在 C++14/17 中使用 std::filesystem::u8path
std::filesystem::path pa = std::filesystem::u8path((const char*)yourStdStringPath.c_str());
std::ofstream ofs(pa);

由于您可以使用u8前缀,因此在C++20中已被弃用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接