Windows命令行窗口与Unicode
一.
还记得刚开始学习C语言的时候,最早接触的 runtime 函数,就是 printf,这应该是一个最基础的函数了。
但是这样的一个函数,即使在VC2005和Windows 7下面,对输出Unicode字符这样的任务,依然会给程序员带了麻烦。
下面的代码是一个Unicode版本的测试程序:
int wmain(int argc, wchar_t **argv) |
看着完全没有问题,但是在console中就是没有任何的输出。
二.
单步进入wprintf
,发现如下代码:retval = _woutput_l(stdout,format,NULL,arglist);
其中,宏stdout
为标准输出,定义如下:#define stdout (&__iob_func()[1])
__iob_func
的代码如下:
/* These functions are for enabling STATIC_CPPLIB functionality */ |
_iob是一个FILE类型的数组,第0个元素为stdin,第1个元素为stdout,第3个为stderr。
而FILE类型是一个结构体:
struct _iobuf { |
从图中可以看出,std本身就不支持UNICODE。
三.
我们还可以更深入一点,进入_woutput_l
函数,会发现该函数调用WRITE_CHAR(ch, &charsout);
宏逐个输出字符。
该宏的定义和_woutput_l
在同一个文件(output.c)中找到:#define WRITE_CHAR(ch, pnw) write_char(ch, stream, pnw)
其中ch为第一个字符,“测”;stream为_woutput_l
的第一个参数,就是stdout。
四.
write_char
的代码很简单:
LOCAL(void) write_char ( |
五.
进入_fputwc_nolock
(即_putwc_nolock
)。该函数的声明为:
wint_t __cdecl _fputwc_nolock ( |
函数中对str有三个判断,
if (_textmode_safe(_fileno(str)) == __IOINFO_TM_UTF16LE) |
第一第二个判断很明显,判断目标文件是否为UTF16 little endian编码和UTF8编码。
对stdout,这里会进入第三个判断,其中FTEXT表示文件句柄为文本模式。
在该条件下,函数接着调用wctomb_s
:wctomb_s(&size, mbc, MB_LEN_MAX, ch)
做编码转换,即试图把wide char的ch转换为multi-byte的mbc。
六.
wctomb_s
调用_wctomb_s_l
,仅仅把locale参数设为NULL:
extern "C" errno_t __cdecl wctomb_s ( |
七.
_wctomb_s_l
函数是wctomb_s
的locale版,根据locale做不同的工作。声明如下:
extern "C" int __cdecl _wctomb_s_l ( |
函数对locale有一个判断,
if ( _loc_update.GetLocaleT()->locinfo->lc_handle[LC_CTYPE] == _CLOCALEHANDLE ) |
如果locale是C locale的话,那么当wchar大于255,即超出ANSI范围时,不做转换,直接返回;
小于255,即为ANSI字符时,则转换为char类型。
如果不是c locale,则调用WideCharToMultiByte
:
if ( ((size = WideCharToMultiByte( _loc_update.GetLocaleT()->locinfo->lc_codepage, |
测试代码没有设置locale,所以判断wchar,自然大于255,函数直接返回。层层返回,没有调用任何实际的输出。
八.
稍稍修改一下测试程序:
int wmain(int argc, wchar_t **argv) |
输出的是两个问号。单步跟踪,我们可以发现,在_woutput_l
中,没有直接调用WRITE_CHAR
,而是调用WRITE_STRING
:
WRITE_STRING(text.wz, textlen, &charsout); |
在write_char写入Unicode字符失败后,就会写入一个”?”,所以输出的会是问号。
九.
如何输出Unicode呢?有两个方法:
设置正确的locale。
我们看到整个过程最后会调用WideCharToMultiByte
把Unicode根据locale转换成multiple byte。
这可以解决一点问题,但不完美。除了绕了个圈,又把wide char转换成multiple byte之外,还无法解决多种语言混合输出的问题。
比如,无法同时输出俄文和中文。使用
WriteConsole
。
如下代码可以完美的解决Unicode的问题:wchar_t szText[] = L"测试";
HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hStdOutput, szText, (DWORD)wcslen(szText), NULL, NULL);