【转】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  _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编程

时间: 2024-08-26 10:52:10

【转】VC下的Unicode编程的相关文章

VC的UNICODE 编程

简介 如果你编写的程序是针对非英语国家的用户,如中国.日本.东欧和中东地区,那么你一定要熟悉 UNICODE 字符集.尤其是用 Visual C++/MFC 编写针对上述国家和地区的用户的程序时,如果你想让自己的应用程序得到更广泛的用户,那么必须考虑代码 UNICODE 的兼容性,也就是说它既在 ASCII 模式下运行 ,也能在UNICODE 模式下运行.本文将介绍 UNICODE 的一些基本编程知识,澄清很多人(包括我自己)在这个问题上存在的模糊认识.对于任何使用 Visual C++ 和/或

VC++的Unicode编程

本文来自:http://tech.ddvip.com/2007-03/117395585321221.html 一.什么是Unicode 先从ASCII说起,ASCII是用来表示英文字符的一种编码规范.每个ASCII字符占用1个字节,因此,ASCII编码可以表示的最大字符数是255(00H—FFH).其实,英文字符并没有那么多,一般只用前128个(00H—7FH,最高位为0),其中包括了控制字符.数字.大小写字母和其它一些符号.而最高位为1的另128个字符(80H—FFH)被称为“扩展ASCII

【转】VC++的Unicode编程

转自http://www.cnblogs.com/kex1n/archive/2010/03/15/2286510.html 原始出处http://www.vckbase.com/document/viewdoc/?id=1733 一.什么是Unicode 先从ASCII说起,ASCII是用来表示英文字符的一种编码规范.每个ASCII字符占用1个字节,因此,ASCII编码可以表示的最大字符数是255(00H—FFH).其实,英文字符并没有那么多,一般只用前128个(00H—7FH,最高位为0),

在windows下的QT编程中的_TCHAR与QString之间的转换

由于在windows下的QT编程中,如果涉及到使用微软的API,那么不可避免使用_TCHAR这些类型,因此在网上查了一下,其中一个老外的论坛有人给出了这个转换,因此在这里做一下笔记 : )#ifdef UNICODE #define QStringToTCHAR(x)     (wchar_t*) x.utf16() #define PQStringToTCHAR(x)    (wchar_t*) x->utf16() #define TCHARToQString(x)     QString:

LINUX下C语言编程基础

实验二 Linux下C语言编程基础 一.实验目的 1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用gdb调试技术 6. 熟悉makefile基本原理及语法规范 7. 掌握静态库和动态库的生成 二.实验步骤 1. 快捷键 Ubuntu中: 2. vim VIM是一个非常好的文本编辑器,很多专业程序员使用VIM编辑代码,即使以后你不编写程序,只要跟文本打交道,都应该学学VIM,可以浏览参考一下普通人

【Windows编程】系列第四篇:使用Unicode编程

上一篇我们学习了Windows编程的文本及字体输出,在以上几篇的实例中也出现了一些带有“TEXT”的Windows宏定义,有朋友留言想了解一些ANSI和Unicode编程方面的内容,本章就来了解和学习一些Windows下关于ANSI和Unicode方面的编程基础. 计算机最早在美国诞生,所以最开始都是以英语为作为交互语言,由于只有26个字母,用一个字节(范围-128 ~ 127)表示,这个范围足够表示26个因为字符和一些常用的控制字符,这个就是ASCII编码.因此最早的各种程序设计语言以及使用的

DELPHI下的SOCK编程

本文是写给公司新来的程序员的,算是一点培训的教材.本文不会涉及太多的编程细节,只是简单讲解在DELPHI下进行Winsock编程最好了解的知识. 题外话:我认为学习编程就如同学习外语一样,最好的方式是你先学会如何去运用它,然后才是了解它的语言特性.语法之类的东西.不过很可惜,我们以前的外语教育使用了相反的过程.软件编程也是一样,在很多人的大学阶段,你更多的是学习那些理论知识,学习“语法”,这里,我丝毫没有贬低理论知识重要性的意思.理论知识和实践是相辅相成的,但一个恰当的学习方式,很多时候可以让学

linux 操作系统下c语言编程入门

2)Linux程序设计入门--进程介绍 3)Linux程序设计入门--文件操作 4)Linux程序设计入门--时间概念 5)Linux程序设计入门--信号处理 6)Linux程序设计入门--消息管理 7)Linux程序设计入门--线程操作 8)Linux程序设计入门--网络编程 9)Linux下C开发工具介绍 1)Linux程序设计入门--基础知识 Linux下C语言编程基础知识 前言: 这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将 会学到以下内容: 源程序编

Ubuntu下Codeblocks+wxWidgets编程,学生公寓管理系统,基于窗体(使用wxFormbuilder拉取控件)。C++,sqlite3

/*************************************************************** * Name:      StuManaMain.cpp * Purpose:   Code for Application Frame * Author:    Zhangaihua (62*********@qq.com) * Created:   2014-05-20 * Copyright: Zhangaihua () * License: *********