修改 vs2010 编译器使之支持u8″字符串”的形式

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

此条目发表在开发, 破解狂分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*