首页 > 代码库 > 【转】VC下的Unicode编程

【转】VC下的Unicode编程

转自http://www.leewei.org/?p=1304

UniCode简述

    在Windows下用VC编程,如果编写的程序要在多种语言环境下运行(比如日文、中文、葡萄牙文等),使用VC默认的MBCS编译选项就会出现乱码,甚至导致程序崩溃。要克服这一缺点,就需要使用Unicode编程,简要说明一下Unicode:

    Unicode也是一种字符编码方法,它占用两个字节(0000H—FFFFH),容纳65536个字符,这完全可以容纳全世界所有语言文字的编码。在Unicode里,所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。

    Windows NT及后续系统的内核都是基于Unicode的。在Windows内核中,宏UNICODE指示是否启用Unicode,而C++是根据_UNICODE宏来判断的,因此在编程中我们要把这两个宏写进预处理参数里。

    比如在tchar.h头文件中,有如下声明:

#define _T(x)       __T(x) #ifdef  _UNICODEtypedef wchar_t     TCHAR;#define __T(x)      L##x#elsetypedef char        TCHAR;#define __T(x)      x#endif

    而在winnt.h头文件中,定义了如下数据类型:

typedef char CHAR, *LPSTR; typedef CONST CHAR *LPCSTR, *PCSTR;  typedef unsigned short WCHAR,*LPWSTR;    // 16-bit UNICODE charactertypedef CONST WCHAR *LPCWSTR, *PCWSTR; //#ifdef  UNICODE typedef WCHAR TCHAR, *PTCHAR;typedef LPWSTR LPTCH, PTCH;typedef LPWSTR PTSTR, LPTSTR;typedef LPCWSTR LPCTSTR;#define __TEXT(quote) L##quote #else       typedef char TCHAR, *PTCHAR;typedef LPSTR LPTCH, PTCH;typedef LPSTR PTSTR, LPTSTR;typedef LPCSTR LPCTSTR;#define __TEXT(quote) quote        #endif /* UNICODE */

    实际上Win32 API有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如:其实根本没有SetWindowText()这个API函数,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。这些API函数的头文件在winuser.h中声明,下面例举winuser.h中的SetWindowText()函数的声明部分:

#ifdef UNICODE#define SetWindowText  SetWindowTextW#else#define SetWindowText  SetWindowTextA#endif // !UNICODE

UniCode实战

    在VC6.0下使用Unicode的步骤如下:
    1、project->Settings…->C/C++->Preprocessor Definitions,删除_MBCS,然后添加_UNICODE,UNICODE。
    2、project->Settings…->Link->Category选择Output,Entry-point Symbol栏填入wWinMainCRTStartup。

    【注】如果是不是exe工程(比如DLL或LIB),不执行第二个步骤,否则会出现warning LNK4086错误。

    C++使用wchar_t来表示一个宽字符,它在内部被定义为unsigned short,占两个字节。相对于普通字符,C++有一整套的宽字符操纵函数, 以下是一份宽字符处理函数函数与普通函数对照表:

宽字符处理函数函数与普通函数对照表字符分类:宽字符函数    普通C函数    描述iswalnum()    isalnum()    测试字符是否为数字或字母iswalpha()    isalpha()    测试字符是否是字母iswcntrl()    iscntrl()    测试字符是否是控制符iswdigit()    isdigit()    测试字符是否为数字iswgraph()    isgraph()    测试字符是否是可见字符iswlower()    islower()    测试字符是否是小写字符iswprint()    isprint()    测试字符是否是可打印字符iswpunct()    ispunct()    测试字符是否是标点符号iswspace()    isspace()    测试字符是否是空白符号iswupper()    isupper()    测试字符是否是大写字符iswxdigit()    isxdigit()    测试字符是否是十六进制的数字 大小写转换:宽字符函数    普通C函数    描述towlower()    tolower()    把字符转换为小写towupper()    toupper()    把字符转换为大写 字符比较:宽字符函数    普通C函数    描述wcscoll()    strcoll()    比较字符串 日期和时间转换:宽字符函数    描述strftime()            根据指定的字符串格式和locale设置格式化日期和时间wcsftime()            根据指定的字符串格式和locale设置格式化日期和时间, 并返回宽字符串strptime()            根据指定格式把字符串转换为时间值, 是strftime的反过程 打印和扫描字符串:宽字符函数            描述fprintf()/fwprintf()    使用vararg参量的格式化输出fscanf()/fwscanf()        格式化读入printf()            使用vararg参量的格式化输出到标准输出scanf()            从标准输入的格式化读入sprintf()/swprintf()    根据vararg参量表格式化成字符串sscanf()            以字符串作格式化读入vfprintf()/vfwprintf()    使用stdarg参量表格式化输出到文件vprintf()            使用stdarg参量表格式化输出到标准输出vsprintf()/vswprintf()    格式化stdarg参量表并写到字符串 数字转换:宽字符函数    普通C函数    描述wcstod()    strtod()    把宽字符的初始部分转换为双精度浮点数wcstol()    strtol()    把宽字符的初始部分转换为长整数wcstoul()    strtoul()    把宽字符的初始部分转换为无符号长整数 多字节字符和宽字符转换及操作:宽字符函数            描述mblen()            根据locale的设置确定字符的字节数mbstowcs()            把多字节字符串转换为宽字符串mbtowc()/btowc()        把多字节字符转换为宽字符wcstombs()            把宽字符串转换为多字节字符串wctomb()/wctob()        把宽字符转换为多字节字符 输入和输出:宽字符函数    普通C函数    描述fgetwc()    fgetc()    从流中读入一个字符并转换为宽字符fgetws()    fgets()    从流中读入一个字符串并转换为宽字符串fputwc()    fputc()    把宽字符转换为多字节字符并且输出到标准输出fputws()    fputs()    把宽字符串转换为多字节字符并且输出到标准输出串getwc()    getc()    从标准输入中读取字符, 并且转换为宽字符getwchar()    getchar()    从标准输入中读取字符, 并且转换为宽字符None        gets()    使用fgetws()putwc()    putc()    把宽字符转换成多字节字符并且写到标准输出putwchar()    getchar()    把宽字符转换成多字节字符并且写到标准输出None        puts()    使用fputws()ungetwc()    ungetc()    把一个宽字符放回到输入流中 字符串操作:宽字符函数    普通C函数    描述wcscat()    strcat()    把一个字符串接到另一个字符串的尾部wcsncat()    strncat()    类似于wcscat(), 而且指定粘接字符串的粘接长度.wcschr()    strchr()    查找子字符串的第一个位置wcsrchr()    strrchr()    从尾部开始查找子字符串出现的第一个位置wcspbrk()    strpbrk()    从一字符字符串中查找另一字符串中任何一个字符第一次出现的位置wcswcs()/wcsstr() strchr()在一字符串中查找另一字符串第一次出现的位置wcscspn()    strcspn()    返回不包含第二个字符串的的初始数目wcsspn()    strspn()    返回包含第二个字符串的初始数目wcscpy()    strcpy()    拷贝字符串wcsncpy()    strncpy()    类似于wcscpy(), 同时指定拷贝的数目wcscmp()    strcmp()    比较两个宽字符串wcsncmp()    strncmp()    类似于wcscmp(), 还要指定比较字符字符串的数目wcslen()    strlen()    获得宽字符串的数目wcstok()    strtok()    根据标示符把宽字符串分解成一系列字符串wcswidth()    None        获得宽字符串的宽度wcwidth()    None        获得宽字符的宽度 另外还有对应于memory操作的 wmemcpy(),wmemchr(),wmemcmp(),wmemmove(),wmemset()。

    Unicode编程中,如果需要声明一个宽字符串,需要这样写:
    wchar_t *wstr = L”Hello”;
    其中字符”L”告诉编译器你要构造的是一个宽字符串,”L”和字符串之间不能有空格。

    虽然上述声明字符串的代码是正确的,但是并不提倡这样做,因为程序可移植性太差。
    还记得前面介绍的几个宏么?_T(x)会在_UNICODE定义了的情况下被扩展为L##x, 而在一般情况下被扩展为x;TCHAR则分别被替换为wchar_t和char。因此我们可以这样写:
    TCHAR *str = _T(“Hello”);
    这样,如果_UNICODE宏被定义了,则它被扩展为:
    wchar_t *wstr = L”Hello”;
    否则,在默认情况下被扩展为:
    char *str = “Hello”;

    如果需要写一个库,而且要分别提供Unicode和非Unicode版本,那么仅仅许多修改两个UNICODE宏就可以了,不需要修改任何代码。

迁移到Unicode

    如果非常不幸,你的项目在一开始没有被设计为使用Unicode(没有使用_T()宏和TCHAR等类型),而现在出于国际化的需要要使其支持Unicode,那么在添加两个UNICODE宏和函数入口点后会可能会出现无数个编译错误(我遇到过566个的)。虽然修改的方式根据项目而不同,但也多少有点相似之处,有步骤地做总比漫无目的得改好。

    1、搜索所有的AfxMessagebox和Messagebox函数,将其中的字符串加上_T()宏。
    2、搜索所有的str.Format函数,为第一个参数加上_T()宏。
    3、为字符串常量加上_T()宏。
    4、将strlen、strcpy等函数替换为wcslen、wcscpy等宽字符版本。
    5、如果wcsncpy、wcsncmp等函数的第三个参数是sizeof(dst),那么现在就要改为sizeof(dst)/2,或者自定义一个宏tsizeof来实现。
    6、如果某个函数确实需要char*等类型的参数,使用T2A()宏对参数进行转换,并在所在函数开头添加”USES_CONVERSION;”。
    7、查找所有的char* p = (LPSTR)(LPCTSTR)CString这样的强制转换代码,并用char *p = T2A(CString);代替。

    通常修改完以上内容,再次编译时错误应该减少了大半了,现在再一个一个地对照修改就容易多了。

    最后,配置文件也要存储为Unicode的形式。Unicode的文件头有个0xFEFF标识,如果你是通过::WritePrivateProfileString()来写入配置文件的,那么只需要在调用此API之前往文件里写入0xFEFF文件头,此后WritePrivateProfileString会自动将后续内容保存成Unicode的形式。为了简单,可以讲程序中调用的::WritePrivateProfileString()全都替换成如下改写版本即可:

static BOOL _WritePrivateProfileString(LPCTSTR lpAppName, // section name                   LPCTSTR lpKeyName, // key name                   LPCTSTR lpString,   // string to add                   LPCTSTR lpFileName // initialization file                   ){    FILE *fp;    fp = _tfopen(lpFileName, _T("r"));    if (fp == NULL)    {        fp=_tfopen(lpFileName, _T("w+b"));         wchar_t m_strUnicode[1];        m_strUnicode[0] = wchar_t(0XFEFF);        fputwc(*m_strUnicode,fp);    }    fclose(fp);     return ::WritePrivateProfileString(lpAppName, lpKeyName, lpString, lpFileName);}