setlocale 与 mbstowcs 的问题

C++的字符串转换函数mbstowcs使用时容易产生bug。。。

rapidxml_utils.hpp 的file(const char*filename)函数内会异常宕机。。。

需要在函数最开始添加

locale::global(locale(""));

from  http://blog.sina.com.cn/s/blog_55c1b83b0100wbah.html

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_twc_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()); }
时间: 2024-11-05 17:30:27

setlocale 与 mbstowcs 的问题的相关文章

string, CStringA, char*与wstring, CStringW, wchar_t*相互转换

1. char*转换为wchar_t* char buf[] = "我是韩长鸣haizeiwanghancm"; wchar_t wbuf[100]; 1.1. C的方式:最可移植的方式 1.1.1. mbstowcs setlocale(LC_CTYPE, ""); mbstowcs(wbuf, buf, sizeof(buf)); 1.1.2. swprintf setlocale(LC_CTYPE, ""); swprintf(wbuf,

几个字符串的误区,以及setlocale函数的使用

转自 http://www.blogjava.net/baicker/archive/2007/08/09/135642.html 转自 http://witmax.cn/character-encoding-notes.html 写了n年程序,近来在字符串上栽了.:( 认真的研究了一些关于字符串的文章,在此记下.许多关于字符串的问题,在文章最后的参考文章中,相信有更加深入和精确的描述.不过关于中文的处理,我想先补充一些自己的看法. 背景:WIN32 console程序,使用printf输出字符

setlocale与编码转换那些事

最近项目上调查printf语句不能正常格式化字符串的问题,做下总结. 以sprintf_s函数来说明问题的现象. int sprintf_s( char *buffer, size_t sizeOfBuffer, const char *format [, argument] ... ); 问题发生的条件 使用了setlocale format参数是utf8编码 format的%是非ASC字符,已locale编码来解释的话,刚好%与前面的编码结合,被当初了一个新的文字. 例如我们出问题时,loc

CentOS -bash: warning: setlocale: LC_MESSAGES: cannot change locale (en_US.UTF-8)

centos5.x 登陆显示: -bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory-bash: warning: setlocale: LC_COLLATE: cannot change locale (en_US.UTF-8): No such file or directory-bash: warning: setlocale: LC_MESSAG

C语言的setlocale和localtime函数(C++也可用)

Example 1234567891011121314151617181920212223242526272829303132 /* setlocale example */ #include <stdio.h> /* printf */ #include <time.h> /* time_t, struct tm, time, localtime, strftime */ #include <locale.h> /* struct lconv, setlocale,

strlen, wcslen, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l, setlocale(LC_CTYPE, &quot;Japanese_Japan&quot;)(MSDN的官方示例)

// crt_strlen.c // Determine the length of a string. For the multi-byte character // example to work correctly, the Japanese language support for // non-Unicode programs must be enabled by the operating system. #include <string.h> #include <local

-bash: warning: setlocale: LC_CTYPE: cannot change locale (EN_US.UTF-8)

最近发现MDT推出去的系统的有不同问题,其问题就不说了,主要是策略权限被域继承了.比如我们手动安装的很多东东都是未配置壮态,推的就默认为安全壮态了,今天细找了一下,原来把这个关了就可以了. -bash: warning: setlocale: LC_CTYPE: cannot change locale (EN_US.UTF-8)

warning: setlocale: LC_CTYPE: cannot change locale

setlocale warning 使用mac上面iterm2一段时间了,登陆服务器老是报如下warning warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory 然后yum 安装的时候也会报LC_CTYPE环境变量的问题.于是抽空搜索了一下,找到了如下文章http://www.cyberciti.biz/faq/os-x-terminal-bash-warning-setloc

wxpython应用启动报错You probably called setlocale() directly instead of using wxLocale and now there is a mismatch between C/C++ and Windows locale.

File "C:\Python27\lib\site-packages\wx-3.0-msw\wx\_controls.py", line 6523, in __init__ _controls_.DatePickerCtrl_swiginit(self,_controls_.new_DatePickerCtrl(*args, **kwargs)) wx._core.PyAssertionError: C++ assertion "strcmp(setlocale(LC_AL