转自:http://blog.163.com/lyzaily@126/blog/static/424388372009106112038559/
Windows 环境下Unicode 编程总结
UNICODE 环境设置
在安装Visual Studio 时,在选择VC++ 时需要加入unicode 选项,保证相关的库文件可以拷贝到system32 下。
UNICODE 编译设置:
C/C++, Preprocessor difinitions 去除_MBCS ,加_UNICODE,UNICODE
在ProjectSetting/link/output 中设置Entry 为wWinMainCRTStartup
反之为MBCS (ANSI )编译。
Unicode :宽字节字符集
1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数?
可以调用Microsoft Visual C++ 的运行期库包含函数_mbslen 来操作多字节(既包括单字节也包括双字节)字符串。
调用strlen 函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0 之前有多少个字节。
2. 如何对DBCS (双字节字符集)字符串进行操作?
函数 描述
PTSTR CharNext ( LPCTSTR ); 返回字符串中下一个字符的地址
PTSTR CharPrev ( LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址
BOOL IsDBCSLeadByte( BYTE ) ; 如果该字节是DBCS 字符的第一个字节,则返回非0 值
3. 为什么要使用Unicode ?
(1 ) 可以很容易地在不同语言之间进行数据交换。
(2 ) 使你能够分配支持所有语言的单个二进制.exe 文件或DLL 文件。
(3 ) 提高应用程序的运行效率。
Windows 2000 是使用Unicode 从头进行开发的 , 如果调用任何一个Windows 函数并给它传递一个ANSI 字符串,那幺系统首先要将字符串转换成Unicode ,然后将Unicode 字符串传递给操作 系统。如果希望函数返回ANSI 字符串,系统就会首先将Unicode 字符串转换成ANSI 字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode 来开发应用程序,就能够使你的应用程序更加有效地运行。
Windows CE 本身就是使用Unicode 的一种操作系统,完全不支持ANSI Windows 函数
Windows 98 只支持ANSI ,只能为ANSI 开发应用程序。
Microsoft 公司将COM 从16 位Windows 转换成Win32 时,公司决定需要字符串的所有COM 接口方法都只能接受Unicode 字符串。
4. 如何编写Unicode 源代码?
Microsoft 公司为Unicode 设计了WindowsAPI ,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使用Unicode 来 对它进行编译。只需要定义两个宏(UNICODE 和_UNICODE ),就可以修改然后重新编译该源文件。
_UNICODE 宏用于 C 运行期 头文件,而UNICODE 宏则用于 Windows 头文件。 当编译源代码模块时,通常必须同时定义这两个宏。
5. Windows 定义的Unicode 数据类型有哪些?
数据类型 说明
WCHAR Unicode 字符
PWSTR 指向Unicode 字符串的指针
PCWSTR 指向一个恒定的Unicode 字符串的指针
对应的ANSI 数据类型为CHAR ,LPSTR 和LPCSTR 。
ANSI/Unicode 通用数据类型为TCHAR ,PTSTR,LPCTSTR 。
6. 如何对Unicode 进行操作?
字符集 特性 实例
ANSI 操作函数以str 开头 strcpy
Unicode 操作函数以wcs 开头 wcscpy
MBCS 操作函数以_mbs 开头 _mbscpy
ANSI/Unicode 操作函数以_tcs 开头 _tcscpy ( C 运行期库 )
ANSI/Unicode 操作函数以lstr 开头 lstrcpy ( Windows 函数 )
所有新的和未过时的函数在Windows2000 中都同时拥有ANSI 和Unicode 两个版本。ANSI 版本函数结尾以 A 表示;Unicode 版本函数结尾以 W 表示。 Windows 会如下定义:
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif // !UNICODE
7. 如何表示Unicode 字符串常量?
字符集 实例
ANSI “string”
Unicode L“string”
ANSI/Unicode T(“string”) 或_TEXT(“string”)if( szError[0] == _TEXT(‘J’) ){ }
8. 为什么应当尽量使用操作系统函数?
这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe 所使用。由于这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM 。
如:StrCat ,StrChr ,StrCmp 和StrCpy 等。
9. 如何编写符合ANSI 和Unicode 的应用程序?
(1 ) 将文本串视为字符数组,而不是chars 数组或字节数组。
(2 ) 将通用数据类型(如TCHAR 和PTSTR )用于文本字符和字符串。
(3 ) 将显式数据类型(如BYTE 和PBYTE )用于字节、字节指针和数据缓存。
(4 ) 将TEXT 宏用于原义字符和字符串。
(5 ) 执行全局性替换(例如用PTSTR 替换PSTR )。
(6 ) 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。这意味着不应该传递sizeof(szBuffer), 而应该传递 (sizeof(szBuffer)/sizeof(TCHAR) 。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那幺请记住要 按字节来分配内存。这就是说,应该调用
malloc(nCharacters *sizeof(TCHAR)), 而不是调用malloc(nCharacters) 。
10. 如何对字符串进行有选择的比较?
通过调用CompareString 来实现。
标志 含义
NORM_IGNORECASE 忽略字母的大小写
NORM_IGNOREKANATYPE 不区分平假名与片假名字符
NORM_IGNORENONSPACE 忽略无间隔字符
NORM_IGNORESYMBOLS 忽略符号
NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符
SORT_STRINGSORT 将标点符号作为普通符号来处理
11. 如何判断一个文本文件是ANSI 还是Unicode ?
判断如果文本文件的开头两个字节是0xFF 和0xFE ,那幺就是Unicode ,否则是ANSI 。
12. 如何判断一段字符串是ANSI 还是Unicode ?
用IsTextUnicode 进行判断。IsTextUnicode 使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode 有可能返回不正确的结果。
13. 如何在Unicode 与ANSI 之间转换字符串?
Windows 函数 MultiByteToWideChar 用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte 将宽字符串转换成等价的多字节字符串。
//========================================================================
//TITLE:
// MultiByteToWideChar 和WideCharToMultiByte 用法详解
//AUTHOR:
// norains
//DATE:
// 第一版:Monday 25-December -2006
// 增补版:Wednesday 27-December -2006
// 修订版:Wednesday 14-March-2007 ( 修正之前的错误例子)
//Environment:
// EVC4.0 + Standard SDK
//========================================================================
1. 使用方法详解
在本文开始之处, 先简要地说一下何为短字符和宽字符.
所谓的短字符, 就是用8bit 来表示的字符, 典型的应用是ASCII 码. 而宽字符, 顾名思义, 就是用16bit 表示的字符, 典型的有UNICODE. 关于 windows 下的ASCII 和UNICODE 的更多信息, 可以参考这两本经典著作: 《windows 程序设计》, 《windows 核心编程》. 这两本书关于这两种字符都有比较详细的解说.
宽字符转换为多个短字符是一个难点, 不过我们只要掌握到其中的要领, 便可如鱼得水.
好吧, 那就让我们开始吧.
这个是我们需要转化的多字节字符串:
char sText[20] = {" 多字节字符串!OK!"};
我们需要知道转化后的宽字符需要多少个数组空间. 虽然在这个里程里面, 我们可以直接定义一个 20*2 宽字符的数组, 并且事实上将运行得非常轻松愉快. 但假如多字节字符串更多, 达到上千个乃至上万个, 我们将会发现其中浪费的内存将会越来越多. 所以 以多字节字符的个数的两倍作为宽字符数组下标的声明绝对不是一个好主意.
所幸, 我们能够确知所需要的数组空间.
我们只需要将MultiByteToWideChar() 的第四个形参设为-1, 即可返回所需的短字符数组空间的个数:
DWORD dwNum = MultiByteToWideChar (CP_ACP, 0, sText, -1, NULL, 0);
接下来, 我们只需要分配响应的数组空间:
wchar_t *pwText;
pwText = new wchar_t[dwNum];
if(!pwText)
{
delete []pwText;
}
接着, 我们就可以着手进行转换了. 在这里以转换成ASCII 码做为例子:
MultiByteToWideChar (CP_ACP, 0, psText, -1, sText, dwSize);
最后, 使用完毕当然要记得释放占用的内存:
delete []psText;
同理, 宽字符转为多字节字符的代码如下:
wchar_t wText[20] = {L" 宽字符转换实例!OK!"};
DWORD dwNum = WideCharToMultiByte(CP_OEMCP,NULL,lpcwszStr,-1,NULL,0,NULL,FALSE);
char *psText;
psText = new char[dwNum];
if(!psText)
{
delete []psText;
}
WideCharToMultiByte (CP_OEMCP,NULL,lpcwszStr,-1,psText,dwNum,NULL,FALSE);
delete []psText;
如果之前我们已经分配好空间, 并且由于字符串较短, 可以不理会浪费的空间, 仅仅只是想简单地将短字符和宽字符相互转换, 那有没有什么简便的方法呢?
WIN32 API 里没有符合这种要求的函数, 但我们可以自己进行封装:
//-------------------------------------------------------------------------------------
//Description:
// This function maps a character string to a wide-character (Unicode) string
//
//Parameters:
// lpcszStr: [in] Pointer to the character string to be converted
// lpwszStr: [out] Pointer to a buffer that receives the translated string.
// dwSize: [in] Size of the buffer
//
//Return Values:
// TRUE: Succeed
// FALSE: Failed
//
//Example:
// MByteToWChar(szA,szW,sizeof(szW)/sizeof(szW[0]));
//---------------------------------------------------------------------------------------
BOOL MByteToWChar(LPCSTR lpcszStr, LPWSTR lpwszStr, DWORD dwSize)
{
// Get the required size of the buffer that receives the Unicode
// string.
DWORD dwMinSize;
dwMinSize = MultiByteToWideChar (CP_ACP, 0, lpcszStr, -1, NULL, 0);
if(dwSize < dwMinSize)
{
return FALSE;
}
// Convert headers from ASCII to Unicode.
MultiByteToWideChar (CP_ACP, 0, lpcszStr, -1, lpwszStr, dwMinSize);
return TRUE;
}
//-------------------------------------------------------------------------------------
//Description:
// This function maps a wide-character string to a new character string
//
//Parameters:
// lpcwszStr: [in] Pointer to the character string to be converted
// lpszStr: [out] Pointer to a buffer that receives the translated string.
// dwSize: [in] Size of the buffer
//
//Return Values:
// TRUE: Succeed
// FALSE: Failed
//
//Example:
// MByteToWChar(szW,szA,sizeof(szA)/sizeof(szA[0]));
//---------------------------------------------------------------------------------------
BOOL WCharToMByte(LPCWSTR lpcwszStr, LPSTR lpszStr, DWORD dwSize)
{
DWORD dwMinSize;
dwMinSize = WideCharToMultiByte(CP_OEMCP,NULL,lpcwszStr,-1,NULL,0,NULL,FALSE);
if(dwSize < dwMinSize)
{
return FALSE;
}
WideCharToMultiByte(CP_OEMCP,NULL,lpcwszStr,-1,lpszStr,dwSize,NULL,FALSE);
return TRUE;
}
使用方法也很简单, 示例如下:
wchar_t wText[10] = {L" 函数示例"};
char sText[20]= {0};
WCharToMByte(wText,sText,sizeof(sText)/sizeof(sText[0]));
MByteToWChar(sText,wText,sizeof(wText)/sizeof(wText[0]));
这两个函数的缺点在于无法动态分配内存, 在转换很长的字符串时可能会浪费较多内存空间; 优点是, 在不考虑浪费空间的情况下转换较短字符串非常方便.
2.MultiByteToWideChar() 函数乱码的问题
有的朋友可能已经发现, 在标准的WinCE4.2 或WinCE5.0 SDK 模拟器下, 这个函数都无法正常工作, 其转换之后的字符全是乱码. 及时更改MultiByteToWideChar() 参数也依然如此.
不过这个不是代码问题, 其结症在于所定制的操作系统. 如果我们定制的操作系统默认语言不是中 文, 也会出现这种情况. 由于标准的SDK 默认语言为英文, 所以肯定会出现这个问题. 而这个问题的解决, 不能在简单地更改控制面板的" 区域选项" 的" 默认语 言", 而是要在系统定制的时候, 选择默认语言为" 中文".
系统定制时选择默认语言的位置于:
Platform -> Setting... -> locale -> default language , 选择" 中文", 然后编译即可.
14. Unicode 和DBCS 之间的区别
Unicode 使用(特别在C 程序设计语言环境里)“ 宽字符集” 。「Unicode 中的每个字符都是16 位宽而不是8 位宽。」在Unicode 中,没有单单使用8 位数值 的意义存在。相比之下,在“ 双位组字符集” 中我们仍然处理8 位数值。有些位组自身定义字符,而某些位组则显示需要和另一个位组共同定义一个字符。
处 理DBCS 字符串非常杂乱,但是处理Unicode 文字则像处理有秩序的文字。您也许会高兴地知道前128 个Unicode 字符(16 位代码从 0x0000 到0x007F )就是ASCII 字符,而接下来的128 个Unicode 字符(代码从0x0080 到0x00FF )是ISO 8859-1 对ASCII 的扩展。Unicode 中不同部分的字符都同样基于现有的标准。这是为了便于转换。希腊字母表使用从0x0370 到0x03FF 的代码,斯拉夫语使用从0x0400 到0x04FF 的代码,美国使用从0x0530 到0x058F 的代码,希伯来语使用从0x0590 到0x05FF 的代 码。中国、日本和韩国的象形文字(总称为CJK )占用了从0x3000 到0x9FFF 的代码。Unicode 的最大好处是这里只有一个字符集,没有一点含 糊。
15. 衍生标准
Unicode 是一个标准。UTF-8 是其概念上的子集,UTF-8 是具体的编码标准。而UNICODE是所有想达到世界统一编码标准的标准。UTF-8 标准就是Unicode (ISO10646 )标准的一种变形方式,
UTF 的全称是:Unicode/UCS Transformation Format ,其实有两种UTF ,一种是UTF-8 ,一种是UTF-16 ,
不过UTF-16 使用较少,其对应关系如下:
在Unicode 中编码为 0000 - 007F 的 UTF-8 中编码形式为: 0xxxxxxx
在Unicode 中编码为 0080 - 07FF 的 UTF-8 中编码形式为: 110xxxxx 10xxxxxx
在Unicode 中编码为 0000 - 007F 的 UTF-8 中编码形式为: 1110xxxx 10xxxxxx 10xxxxxx
utf- 8 是unicode 的一个新的编码标准, 其实unicode 有过好几个标准. 我们知道一直以来使用的unicode 字符内码都是16 位, 它实际上还不能把 全世界的所有字符编在一个平面系统, 比如中国的藏文等小语种, 所以utf-8 扩展到了32 位, 也就是说理论在utf-8 中可容纳二的三十二次方个字符. UNICODE 的思想就是想把所有的字符统一编码, 实现一个统一的标准.big5 、gb 都是独立的字符集, 这也叫做远东字符集, 把它拿到德文版的 WINDOWS 上可能将会引起字符编码的冲突.... 早期的WINDOWS 默认的字符集是ANSI.notepad 中输入的汉字是本地编码, 但在 NT/2000 内部是可以直接支持UNICODE 的。notepad.exe 在WIN95 和98 中都是ANSI 字符, 在NT 中则是 UNICODE.ANSI 和UNICODE 可以方便的实现对应映射, 也就是转换 ASCII 是8 位范围内的字符集,对于范围之外的字符如汉字它是无法表达的。unicode 是16 位范围内的字符集,对于不同地区的字符分区分 配,unicode 是多个IT 巨头共同制定的字符编码标准。如果在unicode 环境下比如WINDOWS NT 上,一个字符占两字节16 位,而在ANSI 环境下如WINDOWS98 下一个字符占一个字节8 位.Unicode 字符是16 位宽,最多允许 65,535 字符,数据类型被称为WCHAR 。
对于已有的ANSI 字符,unicode 简单的将其扩展为16 位:比如ANSI"A"=0x43, 则对应的UNICODE 为
"A"= 0x0043
而ASCII 用七存放128 个字符,ASCII 是一个真正的美国标准, 所以它不能满足其他国家的需要, 例如斯拉夫语的字母和汉字于是出现了Windows ANSI 字符集, 是一种扩展的ASCII 码, 用8 位存放字符, 低128 位仍然存放原来的ASCII 码,
而高128 位加入了希腊字母等
if def UNICODE
TCHAR = wchar
else
TCHAR = char
你需要在Project/Settings/C/C++/Preprocesser definitions 中添加UNICODE 和_UNICODE
UINCODE,_UNICODE 都要定义。不定义_UNICODE 的话,用SetText(HWND,LPCTSTR), 将被解释为SetTextA(HWND,LPTSTR), 这时 API 将把你给的Unicode 字符串看作ANSI 字符串,显示乱码。因为windows API 是已经编译好存在于dll 中的,由于不管UNICODE 还是ANSI 字符串,都被看作一段buffer, 如"0B A3 00 35 24 3C 00 00" 如果按ANSI 读,因为ANSI 字串是以''''''''/0'''''''' 结束的,所以只能读到两字节"0B A3 /0" ,如果按UNICODE 读,将完整的读到''''''''/0/0'''''''' 结束。
由于UNICODE 没有额外的指示位,所以系统必须知道你提供的字串是哪种格式。此外, UNICODE 好象是ANSI C++ 规定的 ,_UNICODE 是windows SDK 提供的。如果不编写windows 程序,可以只定义UNICODE 。
开发过程:
围绕着文件读写、字符串处理展开。文件主要有两种:.txt 和.ini 文件
1. 在unicode 和非unicode 环境下字符串做不同处理的,那么需要参考以上9 ,10 两条,以适应不同环境得字符串处理要求。
对文件读写也一样。只要调用相关接口函数时,参数中的字符串前都加上_TEXT 等相关宏。如果写成的那个文件需要是unicode 格式保存的,那么在创建文件时需要加入一个字节头。
CFile file;
WCHAR szwBuffer[128];
WCHAR *pszUnicode = L"Unicode string/n"; // unicode string
CHAR *pszAnsi = "Ansi string/n"; // ansi string
WORD wSignature = 0xFEFF;
file.Open(TEXT("Test.txt"), CFile::modeCreate|CFile::modeWrite);
file.Write(&wSignature, 2);
file.Write(pszUnicode, lstrlenW(pszUnicode) * sizeof(WCHAR));
// explicitly use lstrlenW function
MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, szwBuffer, 128);
file.Write(szwBuffer, lstrlenW(szwBuffer) * sizeof(WCHAR));
file.Close();
// 以上这段代码在unicode 和非unicode 环境下都有效。这里显式的指明用Unicode 来进行操作。
2. 在非unicode 环境下,缺省调用的都是ANSI 格式的字符串,此时TCHAR 转换为CHAR 类型的,除非显式定义WCHAR 。所以在这个环境下,如果 读取unicode 文件,那么首先需要移动2 个字节,然后读取得字符串需要用MultiByteToWideChar 来转换,转换后字符串信息才代表 unicode 数据。
3. 在unicode 环境下,缺省调用得都是unicode 格式得字符串,也就是宽字符,此时TCHAR 转换为WCHAR ,相关得API 函数也都调用宽字符类 型的函数。此时读取unicode 文件也和上面一样,但是读取得数据是WCHAR 的,如果要转换成ANSI 格式,需要调用 WideCharToMultiByte 。如果读取ANSI 的,则不用移动两个字节,直接读取然后视需要转换即可。
某些语言(如韩语)必 须在unicode 环境下才能显示,这种情况下,在非unicode 环境下开发,就算用字符串函数转换也不能达到显示文字的目的,因为此时调用得API 函 数是用ANSI 的(虽然底层都是用UNICODE 处理但是处理结果是按照程序员调用的API 来显示的)。所以必须用unicode 来开发。
【总结】
看了这么多的资料也专研了这么长时间,也应该对自己的认识做个小结了,如果我们是写 windows 程序时,在编写操作字符的程序时,我们可以调用运行时库的函数,运行时库中的函数接口定义都是行业标准的,也就是说如果你使用的都是运行时 库函数则你的程序在windows 上开发的,但是编译后也可以在linux 系统上运行,只要这连个系统的编译器所使用的运行时库中的函数都严格按照国际标 准实现的就ok 。
你如果在windows 上编程,编译并运行,你当然可以使用windows 系统提供给你的函 数,这些函数得到了windows 系统的支持。当然这样的程序编译后也只能在windows 系统上运行,其他系统上无法正常执行。所有如果你再 windows 上开发程序,而又想在其他系统上运行,这只能使用运行时库函数了。
小结就这么多,如果有错的地方请看官指正,欢迎交流!!!