• 字节转换


    setlocale 与 mbstowcs 的问题

    Qiang Post in 编程,Tags: c++i18n

    1 问题

    在 Windows XP 多语言简中环境下,用 VC2005 中的 std::fstream 打开中文名文件,系统报错找不到此文件。

    std::ifstream file("\xd6\xd0.txt"); // GBK 编码的 "中.txt"
    if (!file)
    {
       std::cerr << "Cannot open file!"; // Oops!
    }

     

    2 原因

    在 VC2005 中 std::fstream 的打开文件的函数实现里,传入的 char const* 文件名作为多字节首先被 mbstowcs 转换成宽字节后,再转发给 Unicode 版本的 API 进行实际的打开文件操作。见 fiopen.cpp:

    _MRTIMP2_NCEEPURE FILE *__CLRCALL_PURE_OR_CDECL _Fiopen(const char *filename,
        ios_base::openmode mode, int prot)
        {    // open wide-named file with byte name
        wchar_t wc_name[FILENAME_MAX];
     
        if (mbstowcs_s(NULL, wc_name, FILENAME_MAX, filename, FILENAME_MAX - 1) != 0)
            return (0);
        return _Fiopen(wc_name, mode, prot);
        }

    问题的关键在于,对于 mbstowcs 函数来说,它需要知道多字节的编码类型才能正确的将其转换成宽字节的 unicode,很可惜这个编码类型并没有体现在函数的参数列表里,而是隐含依赖全局的 locale 。更加不幸的是,全局 locale 默认没有使用系统当前语言,而是设置为没什么用处的 “C” locale 。于是 GBK 编码的文件名在 “C” locale 下转换错误,悲剧发生了……

    3 解

    知道了原因,解就很简单了。在调用 mbstowcs 或使用它的函数之前,先用 setlocale 将全局默认 locale 设为当前系统默认 locale :

    setlocale(LC_ALL, "");

    如果是在非中文系统上转 GBK 编码,就需要指定中文 locale :

    setlocale(LC_ALL, "chs"); // chs 是 VC 里简中的 locale 名字

    还有一种方法,直接使用宽字节版本的API,之前的编码由自己转换好,避免系统语言环境设置的影响。在 VS2005 中 fstream 有个扩展,可以直接打开宽字节文件名:

    std::ifstream file(L"\u4E2D.txt"); // UCS2 编码的“中.txt”

    4 引申

    API 中隐藏依赖关系是不好的,这种隐藏总意谓着外部环境能通过潜规则来影响 API 的功能。这影响了该API的复用性,可测性,也容易让用户出现意外错误。进一步设想一下,如果环境原来的 locale 是被其它代码块故意设置的,如果为了修正打开中文名文件的 Bug 而冒冒然修改当前全局的 locale ,很可能会让依赖于原 locale 工作的代码出现 bug 。在这样的 API 设计下,如果要尽量避免顾此失彼的发生,我们可以在修改前保存当前的 locale ,用完后再恢复回原来的 locale 。在 C++ 里,最好是将这样的逻辑用 RAII 来封装:

    class  scoped_locale
    {
    public:
        scoped_locale(std::string const&amp; loc_name)
            : _new_locale(loc_name)
            , _setted(false)
        {
            try
            {
                char const* old_locale = setlocale(LC_CTYPE, _new_locale.c_str());
     
                if (NULL != old_locale)
                {
                    _old_locale = old_locale;
                    _setted = true;
                }
            }
            catch (...)
            {
            }
        }
     
        ~scoped_locale()
        {
            try
            {
                if (_setted)
                {
                    char const* pre_locale = setlocale(LC_CTYPE, _old_locale.c_str());
     
                    if (pre_locale)
                    {
                        assert(pre_locale == _new_locale);
                        _setted = false;
                    }
                }
            }
            catch (...)
            {
            }
        }
     
    private:
        std::string _new_locale;
        std::string _old_locale;
        bool _setted;
    };

    原代码可以改为:

    {
        scoped_locale change_locale_to("");
        std::ifstream file("\xd6\xd0.txt"); // GBK 编码的“中.txt”
        if (!file)
        {
            std::cerr << "Cannot open file!"; // Oops!
        }
    }

    当然,如果是多线程环境的话,还需要查明 locale 的全局性是进程级的还是线程级的。如果是前者,那还是会有潜在的相互影响的风险。从这点上来看,C/C++ 标准库中 mbstowcs 的设计是有瑕疵的。这也从反面体现了 Dependency Injection 思想的重要性。在 Win32 API 有个类似的函数 WideCharToMultiByte() ,它的作用也是进行多字节到宽字节的编码转换,但在API设计上,它就将 code page 作为第一个入参显示传入,而不是默认使用全局系统的某个状态。用它来写一个通用的转换函数就可以避免 mbstowcs 的问题了:

    std::wstring native_to_utf16(std::string const& native_string)  
    {  
        UINT const codepage = CP_ACP;  
        DWORD const sizeNeeded = MultiByteToWideChar(  
            codepage, 0, native_string.c_str(), -1, NULL, 0);  
     
        std::vector<wchar_t> buffer(sizeNeeded, 0);  
     
        if (0 == MultiByteToWideChar(codepage, 0,  
                native_string.c_str(), -1,  
                &buffer[0], buffer.size()))
        {  
            throw std::runtime_error("wrong convertion from native string to utf16");  
        }  
     
        return std::wstring(buffer.begin(), buffer.end());
    }
  • 相关阅读:
    无法在WEB服务器上启动调试
    Zedgraph悬停时显示内容闪烁的解决
    用ZedGraph控件作图圆
    34.node.js之Url & QueryString模块
    33.Node.js 文件系统fs
    32.Node.js中的常用工具类util
    31.Node.js 常用工具 util
    30.Node.js 全局对象
    28.Node.js 函数和匿名函数
    27.Node.js模块系统
  • 原文地址:https://www.cnblogs.com/BloodAndBone/p/2086065.html
Copyright © 2020-2023  润新知