遇到一个问题,解决了,做个总结:WIN32编程原来没想到把c++和WIN32放在一起做开发,上次被个缓冲区的溢出搞怕了,所以用c++的流来代替!

结果故事就开始了,文章有点长,要读做好心理准备哈![其实,要知道答案不用看前面,直接看case就晓得怎么编码了,不过,如果你懂了运行时的机制,那么你也就懂料,为什么会有这个问题,所以。。。]

  • 总结下在VC[2005]下的CRT调用

[存在模式转换wcout,wiofstream,如果不需要的话,直接调用_fwrite(crt的ASCI运行时)写入到设备即可]:

operator<< -> streambuf::virtual streamsize __CLR_OR_THIS_CALL xsputn(const _Elem *_Ptr,streamsize _Count)
                  -> int __cdecl fputc (int ch,FILE *str) [msvcr80d.dll]  -> int __cdecl _write_nolock (int fh,  const void *buf, unsigned cnt) [write.c]

最关键的步骤在_write_nolock[这个名字说明函数已经成功通过"关键段"的同步验证]的设计步骤: [这个函数内部会判断所谓的字符模式,注意这个模式是流状态(流缓冲区)的模式]

  1. tmode = _textmode(fh);//测试当前字符状态:__IOINFO_TM_ANSI(ASCN C模式),__IOINFO_TM_UTF8,  __IOINFO_TM_UTF16LE
  2. 当前的字符模式匹配,以及console缓冲区的匹配]                                                                                                                                                               if (_isatty(fh) && (_osfile(fh) & FTEXT))//OK,读取当前模式
    {

                DWORD dwMode;
                _ptiddata ptd = _getptd();
                isCLocale = (ptd->ptlocinfo->lc_handle[LC_CTYPE] == _CLOCALEHANDLE);
                toConsole = GetConsoleMode((HANDLE)_osfhnd(fh), &dwMode);
    }
  3. 转化操作:                                                                                                                                                                                                                toConsole && !(isCLocale && (tmode == __IOINFO_TM_ANSI))) //针对Console的,若当前locale和标准C不匹配,或者当前字符模式不是默认的ANSI模式,则进行转化:首先进行double convert,然后用writeFile写到设备;                                                                                                                                           

简单的说,就是当前的wcout,cout会根据两点来进行核心操作:

  • 当前字符类型,就是上面提到的字符状态:char->wchar_t,wchar_t->char,下面是调用平台相关API[windows下的WideXXXtoChar,或者相反的函数]
  • 当前的现场[c++程序设计语言里locale类]

但这里有个陷阱,就是看上面的转化条件[若当前locale是标准c时,会进行转化么?不会的!]

说了这么多,MS的CRT宽字符转化是什么时候进行,事实是:当你的字符类型是宽字符,且locale不是标准c时才进行。[不相信的话,去VS里面F11自己看!]

好了,这就很容易解释下面的一些现象了:

case 1:

我们在默认的locale条件下,完全没有转化,直接打出,当前这是由于运行时的环境是简体中文的,因此是能输出中文的!

cout<<locale().name().c_str()<<endl;
cout<<cout.getloc().name().c_str()<<endl;
cout<<"cout   不支持输出中文? "<<endl;

case 2:

我去控制面板将系统语言更改为EN_US,locale("")会获取系统平台相关的默认语言,这个时候自然就不能输出中文了!

[顺便说一句,JRE的设置也是从系统默认语言环境中读取的,所以如果你要给老大做演示,而所有的默认系统对话框都是中文,你知道怎么做萨!]

locale::global(locale(""));//cout.imbue(std::locale("Chinese"));这句是强制的流locale绑定,呵呵,建议用这个,locale("")与系统相关,出了问题不好找
cout<<locale().name().c_str()<<endl;
cout<<cout.getloc().name().c_str()<<endl;
cout<<"cout   不支持输出中文? "<<endl;

以上的两种情况都不存在转化[double convert]问题,下面来看两个比较容易错的情况:[呵呵,开始吓了我一跳,仔细分析了下,恍然大悟]

case 3: wcout的试验

wchar_t s0[15]=TEXT("丁磊");
char s2[30];
::WideCharToMultiByte(CP_ACP, NULL, s0, _countof(s0), s2, _countof(s2), NULL, NULL);//to ASCI
cout<<s2<<endl;
wcout<<s0<<endl;

第一cout肯定是没问题的,情况和case1,case2一样,下面的wcout呢?不要惊讶,如果你追踪堆栈的话,你会发现在 int __cdecl fputc (int ch,FILE *str)之后,会得到一个错误码,然后直接退出:

看看这个函数

wint_t __cdecl fputwc (
        wchar_t ch,
        FILE *str
        )
{
        REG1 FILE *stream;
        REG2 wint_t retval;

        _VALIDATE_RETURN((str != NULL), EINVAL, WEOF);

        /* Init stream pointer */
        stream = str;

        _lock_str(stream);
        __try {

        retval = _fputwc_nolock(ch,stream);

        }
        __finally {
                _unlock_str(stream);
        }

        return(retval);
}

/***
*_fputwc_nolock() –  putwc() core routine (locked version)
*
*Purpose:
*       Core putwc() routine; assumes stream is already locked.
*
*       [See putwc() above for more info.]
*
*Entry: [See putwc()]
*
*Exit:  [See putwc()]
*
*Exceptions:
*
*******************************************************************************/

wint_t __cdecl _fputwc_nolock (
        wchar_t ch,
        FILE *str
        )
{

        if (!(str->_flag & _IOSTRG))
        {
            if (_textmode_safe(_fileno(str)) == __IOINFO_TM_UTF16LE)
            {
                /* binary (Unicode) mode */
                if ( (str->_cnt -= sizeof(wchar_t)) >= 0 ) {
                    return (wint_t) (0xffff & (*((wchar_t *)(str->_ptr))++ = (wchar_t)ch));
                } else {
                    return (wint_t) _flswbuf(ch, str);
                }
            }
            else if (_textmode_safe(_fileno(str)) == __IOINFO_TM_UTF8)
            {
                /*
                 * This is for files open for unicode writes. We need 2 chars
                 * instead of 1. Note that even if we are writing UTF8, we don’t
                 * really need to worry about it here. _write will take care of
                 * proper conversion.
                 */

                char * p = (char *)&ch;

                if(_putc_nolock(*p, str) == EOF)
                    return WEOF;

                ++p;

                if(_putc_nolock(*p, str) == EOF)
                    return WEOF;

                return (wint_t)(0xffff & ch);

            }
            else if ((_osfile_safe(_fileno(str)) & FTEXT))//对于标准输入在这里,前面两种是文本模式
            {
                int size, i;
                char mbc[MB_LEN_MAX];

                /* text (multi-byte) mode */
                if (wctomb_s(&size, mbc, MB_LEN_MAX, ch) != 0)
                {
                        /*
                         * Conversion failed; errno is set by wctomb_s;
                         * we return WEOF to indicate failure.
                         */
                        return WEOF;
                }
                for ( i = 0; i < size; i++)
                {
                        if (_putc_nolock(mbc[i], str) == EOF)
                                return WEOF;
                }
                return (wint_t)(0xffff & ch);
            }
        }
        /* binary (Unicode) mode */
        if ( (str->_cnt -= sizeof(wchar_t)) >= 0 )
                return (wint_t) (0xffff & (*((wchar_t *)(str->_ptr))++ = (wchar_t)ch));
        else
                return (wint_t) _flswbuf(ch, str);
}

为什么呢?你可能有点抓狂,可是确实如此,自己犯的错,MS CRT当然不会替你买单!

问题在wchar_t s0[15]=TEXT("丁磊");//这个编码是多长,4*sizeof(wchar_t),两个中文字符在转化入wchart_t时,是以0xXX的形式放入的,虽然能看到自己的名字很爽,但是字符数组里面的存储可能让你很不爽。每个汉字的高低字节被分别存入两个wchar_t里面!俄!~~

随后的事情就很容易理解了,在下面的函数里面,CRT为你的每个wchar_t进行分别的高低字节验证,发现不符合安全验证规范[说实话,这里追进去看代码,没看的很懂的]!新的_s函数当然不买你的帐!直接返回异常码了!

/* text (multi-byte) mode */
                if (wctomb_s(&size, mbc, MB_LEN_MAX, ch) != 0)
                {
                        /*
                         * Conversion failed; errno is set by wctomb_s;
                         * we return WEOF to indicate failure.
                         */
                        return WEOF;
                }

因此对于case 3: wcout的正确写法:

wchar_t s0[15]=TEXT("丁磊");
 char s2[30];
 ::WideCharToMultiByte(CP_ACP, NULL, s0, _countof(s0), s2, _countof(s2), NULL, NULL);//to ASCI
 cout<<s2<<endl;
 wcout<<s2<<endl;//有点惊讶吧,不过这是对的!

但这样还是不直观,如果更直观的搞定呢,其实很简单,我们强制的给流一个locale状态让它不是ASCI的标准状态即可![这样它就不会到FTEXT模式,而是转到相应的Unicode编码处理处了!]

case 4: wcout,cout

wchar_t s0[15]=TEXT("丁磊");
 char s2[30];
 ::WideCharToMultiByte(CP_ACP, NULL, s0, _countof(s0), s2, _countof(s2), NULL, NULL);//to ASCI
 wcout.imbue(locale("Chinese"));
 cout<<s2<<endl;
 wcout<<s0<<endl;

case 5: 文件操作类似

wchar_t s0[15]=TEXT("丁磊");

wofstream out("proc.txt");
 if(!out) return EXIT_FAILURE;
 try{
  out.imbue(locale("Chinese"));//如果注释掉这行,那wofstream又会傻拉巴即的去当你的字符是FTEXT处理,自然就是被_s给砍掉了,所以proc.txt就是空
  out<<L"输出0:"<<s0<<endl;//注意L,使得“输出0“也是同上处理,所以自然为空
  out<<L"输出1:"<<s0<<endl;
 }catch(const runtime_error& ex){
  cerr<<ex.what()<<endl;//这个不是用异常处理的,所以ex肯定是永远抓不到,是用返回异常码的方式处理
 }
 out.close(); cout<<endl;

  • GCC呢?赫赫,好的多,为什么好的多呢?GCC是一个纯的编程平台,MS CRT里面还有语言包的转化处理,如果你在GCC里面这样写wchar_t *s0=L"丁磊";,GCC编译都通不过,因此你要辛苦点去处理ASCI码了,去翻汉字国标吧!顺便提一下Code::Block里面其实有wofstream的支持的,只是有点麻烦,不如直接调windows的API方便,见http://www.hardforum.com/archive/index.php/t-973922.html[所以MS方便是方便,就是有的时候,让人费解,如果想GCC那样,什么都开放出来,让你自己去看,反而没人骂了]
  • 至于这个MS的support,呵呵,你可以当不存在,因为与其问别人,不如自己动手去看运行时的程序!http://support.microsoft.com/default.aspx/kb/274012
  • 那天看到有人在CSDN上发贴子,不过没人管,估计是比较冷的地方吧!如果不是想将win32和c++结合起来用一下,也不会搞出这个问题来!做过了,就不难了!

一些关于locale的资料:

  1. BJ的《c++程序设计语言》,附录里面-中文名叫《现场》locale
  2. c++网上的库描述[GCC这块没有MS crt做的好,locale的很多东西都不能用!感觉c++的locale理念和MS的相差很远,所以几乎什么都没有!]

Advertisements