windows下Lua输出不了utf-8?!

同事遇到的一个问题,用Lua在windows上无法正常输出utf-8编码的中文,如下:

fn.lua内容是:

local a = "中国人"
local b = "a中国人"
print(a)
print(b)

cmd已经chcp 65001改成utf-8编码了,fn.lua编码格式也是utf-8,但是cmd下第一个字就是无法正常显示,加了一个英文字母就正常了!很奇怪,于是一顿查,发现Lua的windows版print实现最后调用的是fwrite函数,而windows下fwrite无法正常输出utf-8格式字符串,我试过了先指定stdout编码为utf-8还是不行,会报错,比如下边这样:

char s[] = { 0xe4, 0xb8, 0xad, 0xe5, 0x9b, 0xbd, 0xe4, 0xba, 0xba, 0 };//"中国人"; 
int len = strlen(s);
_setmode(_fileno(stdout), _O_U8TEXT);
fwrite(s, sizeof(char), len, stdout);

ms的crt库会跟你说,不行utf-8模式的一定要是偶数个字节!这不是逗比么?!utf-8字符串就不能是奇数个字节了!

好吧,然后我就想当然的以为那我用偶数个字节的中文总行了吧,我改成"中国"总共6个byte,再试,还错!最后无奈只能去看源码,这里新技能get,如何调试MS的crt的代码。方法也很简单:微软提供了一个Microsoft Symbol Server,只需要在VS里TOOLS->Options->Debugging->Symbols勾选一下,选择一个本地symbol cache目录,然后就可以正常的debug这些crt的库函数了。一看就明白了,MS的fwrite实现里有这么一个奇葩的判断,如果stdout没有缓冲,就先写了一个字节,之后在把剩下的全部写进去!所以第一个字是乱码,后边是正常的!!!WTF!!

所以不管传入的字符串是奇数还是偶数,fwrite里调用_fwrite_noblock传的都是1个字节啊!永远是奇数,我也是醉了!

弄清原因之后,解决方案就简单了:

  • 一、改Lua源码改成直接调用_write重新编译一个lua。
  • 二、用bat脚本包装一下,把Lua输出重定向到文件,再type出来。或者用python包装一层。当然2.7版本的python有另外一个问题,不能用cp65001,不过python有强大转码能力,这都不是事儿。

顺便去看了下glibc的fwrite实现,并没有这么奇葩的一个先写1字节的设定啊!还是不理解MS的做法。

PS: ubuntu上debugglibc函数的方法:

  1. sudo apt-get install libc6-dbg # libc.so的debug symbol
  2. sudo apt-get source libc6-dev # 安装libc源码
  3. 然后正常用gdb调试程序,指定第2步下载的eglibc源码目录就好了,directory命令。