vs有比较完善的unicode支持,从2005(或更早?)开始就支持源文件是utf-8,并且可以用中文做变量名函数名。(可以是可以,不推荐)
不过,也有一点很让人生气,就算源文件是utf-8编码的,编译时,他也会把utf-8转成主机的编码。所以,对于中文编码而言,如果源文件是
utf-8的并且有这样的内容,编译还是一样出问题:
const char * test = "®"; tt.cpp(10) : warning C4566: character represented by universal-character-name '\u00AE' cannot be represented in the current code page (936)
c++0x草案中有 u8″string literal” 形式的字符串,如果可以用这个,则问题解决,可惜的是,vs2010并不支持 u8-prefix string literal。当然,u和U也不支持。不过由于windows本身所说的unicode是utf-16 le,所以vc里L”string”就是u”string”。
如果才能让vc以持u8呢?写一个宏和字符串转化的类,在运行期转化是一个可行但比较折中的办法,这个办法最大的问题是依然不能在代码里写”®”这样的字串,因为编译期转成中文编码失败成?号,运行期转成utf-8还是问号。
当然,可以写 L”®”,这样这个u8类可以这样写:
string to_utf8(const wchar_t*); #define U8(string) to_utf8(string).c_str()
然后在用的地方写:
U8(L”®”);
这样基本能用,但带来了一些运行期消耗。并且L的字符串也比较大。当然宏本身还可以改改,使得不需要写L,而是在预处理后加入L,但会带来额外的限制(想想U8(“hello” “world”))。
能不能hack编译器加入u8的支持呢?经过一番研究,发现了点眉目。
首先,vc编译器编译的主要功能代码在c1xx.dll中。这个dll的x86版本位于 C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\c1xx.dll。惊喜的是微软提供了它的pdb,这样分析它的行为就大大简化了。
经过研究发现,vc基本上函展开,获取token生成语法树是一次过的。
在关键函数
4F4C9430 GetTokenFromCurrentChar
处,会有一个循环,从一个全局变量中读取解析到的内容,然后构造出一个token。我们要做的就是hack这个过程,如果当前token符合 u8″string”的形式,则处理一遍字符串,转成8进制的串表示方法。
经过一番苦战成功后,结果如下:
tt.cpp: #define U8(str) u8##str #define U16(str) L##str int main() { U8("哈"); u8"哈"; return 0; } D:\temp>cl /E tt.cpp Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. tt.cpp #line 1 "tt.cpp" int main() { "\345\223\210"; "\345\223\210"; return 0; } |
使用的办法是:
把原始的c1xx.dll重命名为c1xx_.dll,写了一个假的c1xx.dll,把导出函数的调用都重定位到c1xx_.dll。然后在GetTokenFromCurrentChar的循环开始处:
4F4C943E 8B3D 046C644F MOV EDI,DWORD PTR DS:[Current_char]
写入一个跳转,跳转到自己的函数中检查是否需要替换字串。写完后变成:
4F4C943E E8 4D7EB60E CALL c1xx.asmReplCurCode
4F4C9443 90 NOP
这个被调用的函数是这样的:
__declspec(naked) void asmReplCurCode()
{
__asm
{
pushad;
call ReplCurCode;
popad;
mov edi, [ptrCurChar];
mov edi, [edi];
ret;
}
}
即用汇编做一个简单的包装,调用C++函数,然后完成被覆盖的代码要完成的事,然后ret回去。
ReplCurCode是这样的:
void ReplCurCode() { if (g_pred == 0) { g_pred = 1; char ch = ** ptrCurChar; //note: in creating/using precompiled header mode, don't insert this code since it had done before. if (ch == '\r' || ch == '\n') { replace = *ptrCurChar; const char * scode = "\r\n#define __bultin_u8 1\r\n1\r\n"; match = scode + strlen(scode)-3; *ptrCurChar = scode; return ; } } if (*ptrCurChar == match) { *ptrCurChar = replace; match = 0; replace = 0; } const char * src = *ptrCurChar; if (src[0] == '#' && src[1] == 'd') { int ff = 0; } string prefix; while (isspace((unsigned char)(src[0]))) { prefix += src[0]; ++ src; } if (src[0] == 'u' && src[1] == '8' && src[2] == '"') { //so this is a U8 str. string sb; size_t sz = TextProcess::unescape(src+3, MAXLONG, sb); if (src[sz+3] == '"') { string st = TextProcess::escape(sb.c_str(), sb.length()); string & codestr = getPool(prefix + st, src); match = codestr.c_str() + codestr.length(); replace = src+sz+4; *ptrCurChar = codestr.c_str(); } } } |
在首次处理的时候插入一个宏定义,这样代码中就可以知道编译器是不是修改支持了u8的功能。ptrCurChar是微软的c1xx.dll中的全局变量的地址,通过它知道当前要解析什么样的代码,解析完之后替换掉它,使得
u8″哈”
交给微软cl.exe解释的时候变成 “\345\223\210” 当然下一次再跑到这里的时候,比较是不是解释完我设置的替换串了,是的话换回原始要解释的内容,当然是跳过了u8″哈”这个串的。
即: if(*ptrCurChar == match) *ptrCurChar = replace;
这样处理完了之后,vs2010就完美的支持u8″string”了。不过只能小范围内使用或写一些自己用的工具,对于大的团队而言,还是等微软下一个vs吧,也许那时就真正支持u8″literal string”了。
其实不只是 u8 literal string,这样改编译器,还能实现一些更有意思的东西。
dowload: hack_c1xx.dll
仅适用于原始md5为D05630986B03CBB28CD4D8E1BDD65831的c1xx.dll。
把原始c1xx.dll改名为c1xx_.dll,把hack_c1xx.dll改名为c1xx.dll