VC下的Unicode编程

转载 2015年07月09日 10:50:11

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  _UNICODE
typedef wchar_t     TCHAR;
#define __T(x)      L##x
#else
typedef 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 character
typedef 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);
}
复制代码

VC下Unicode编程文档

  • 2014年09月09日 12:16
  • 254KB
  • 下载

VC++的Unicode编程

  • 2009年10月12日 09:05
  • 198KB
  • 下载

VC++信息安全编程(3)扫描Unicode漏洞

Unicode是如今最热门的漏洞之一,也是比较简单易学的一个漏洞,比如去年5.1中美黑客大战中,使用的就是这个漏洞。如果我们能知道他们所采用的入侵手段,就可以进行有效的防御!今天就让我们一起来了解一下...

VC++的UNICODE编程

  • 2011年07月15日 09:36
  • 105KB
  • 下载

VC下Unicode编程文档.docx

  • 2013年10月22日 15:42
  • 36KB
  • 下载

VC++的UNICODE编程

本文来自:http://tech.ddvip.com/2007-03/117395585321221.html一、什么是Unicode  先从ASCII说起,ASCII是用来表示英文字符的一种编码规范...

VC++6.0如何设置使其支持Unicode编程

VC++ 6.0支持Unicode编程,但默认的是ANSI,所以开发人员只需要稍微改变一下编写代码的习惯便可以轻松编写支持UNICODE的应用程序。使用VC++ 6.0进行Unicode编程主要做以下...

VC++ UNICODE编程设置

记录一下使用VC++6.0过程中遇到的问题及技巧: 1. 使用UNICODE编码编译时出错的处理 最近将原来MBCS编码的程序要改为UNICODE编码,结果设置完成后编译报错: msvcrtd....
  • dyzhen
  • dyzhen
  • 2011年05月03日 10:37
  • 360

VC++的Unicode编程.doc

  • 2009年04月06日 22:08
  • 129KB
  • 下载

VC++下的Unicode编程

  • 2011年05月05日 22:59
  • 74KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:VC下的Unicode编程
举报原因:
原因补充:

(最多只允许输入30个字)