整理了以前的关于UNICODE编程相关文章,共享大家。

来源:互联网 发布:电音用什么软件 编辑:程序博客网 时间:2024/04/29 11:21

(大牛飘过,菜鸟入门)

一 UNICODE 是什么
Unicode是1988年由Apple和Xerox共同建立的一项标准。1991年,成立了专门的协会来开发 和推动Unicode 。该协会由Apple 、Compaq、Hewlett-Packard 、IBM、Microsoft 、Oracle、Silicon Graphics、Sybase、Unisys和Xerox等多家公司组成(协会成员的最新列表可从 http://www.Unicode.org获得)。该组织负责维护Unicode标准。Unicode 的完整描述可以参考 Addison-Wesley 出版的The Unicode Standard一书,该书可通http://www.Unicode.org获得。 在Windows Vista 中,每个Unicode字符都使用UTF-16编码,UTF 的全称是Unicode Transformation Format ( Unicode转换格式)。UTF-16将每个字符编码为2个字节(或者说16 位)。在本书中,我们在谈到Unicode 时,除非专门声明,否则一般都是指UTF-16编码。 Windows之所以使用UTF-16 ,是因为全球各地使用的大部分语言中,每个字符很容易用一 个16位值来表示。这样一来,应用程序很容易遍历字符串并计算出它的长度。但是,16位不 足以表示某些语言的所有字符。对于这些语言,UTF-16支持使用代理(surrogates ),后者 是用32位(或者说4个字节)来表示一个字符的一种方式。由于只有少数应用程序需要示 这些语言中的字符,所以UTF-16在节省空间和简化编码这两个目标之间,提供了一个很好 的折衷。注意,.NET Framework始终使用UTF-16来编码所有字符和字符串,所以在你自己 的Windows应用程序中,如果需要在生代码(native code )和托管代码(managed code ) 之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。 另外还有其他用于表示字符的UTF标准,具体如下。UTF-8 UTF-8将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编 码为3个字节,一些字符编码为4个字节。值在0x0080 以下的字符压缩为1个字节, 这对美国使用的字符非常适合。0x0080和0x07FF之间的字符转换为2个字节,这对 欧洲和中东地区的语言非常适用。0x0800 以上的字符都转换为3个字节,适合东亚 地区的语言。最后,代理对(surrogate pairs )被写为4个字节。UTF-8是一种相当 流行的编码格式。但在对值为0x0800及以上的大量字符进行编码的时候,不如 UTF-16高效。 
UTF-32将每个字符都编码为4个字节。如果打算写一个简单的算法来遍 历字符(任何语言中使用的字符),但又不想处理字节数不定的字符,这种编码方 式就非常有用。例如,如果采用UTF-32编码方式,就不需要关心代理(surrogate ) 的问题,因为每个字符都是4个字符。显然,从内存使用这个角度来看,UTF-32 并 不是一种高效的编码格式。因此,很少用它将字符串保存到文件或传送到网络。这 种编码格式一般在应用程序内部使用。 目前,Unicode为阿拉伯语、汉语拼音、西里尔文(俄语)、希腊语、希伯来语、日语假名、 朝鲜语和拉丁语(英语)字符等——这些字符称为书写符号(scripts )——定义了码位(code point ,即一个符号在字符集中的位置)。每个版本的Unicode都在现有的书写符号的基础上 引入了新的字符,甚至会引入新的书写符号,比如腓尼基文(一种古地中海文字)。字符集 中还包含大量标点符号、数学符号、技术符号、箭头、装饰标志、读音符号以及其他字符。

 
二 Windows 中的 Unicode 和 ANSI 函数

自Windows NT起,Windows 的所有版本都完全用Unicode来构建。也就是说,所有核心函数 (创建窗口、显示文本、进行字符串处理等等)都需要Unicode字符串。调用一个Windows 函数时,如果向它传入一个ANSI字符串(由单字节字符组成的一个字符串),函数首先会 把字符串转换为Unicode ,再把结果传给操作系统。如果希望函数返回ANSI字符串,那么操 作系统会先把Unicode字符串转换为ANSI字符串,再把结果返回给你的应用程序。所有这些 转换都是悄悄地进行的。当然,为了执行这些字符串转换,系统会产生时间和内存上的开销。 如果一个Windows 函数需要获取一个字符串作为参数,则该函数通常有两个版本。例如,一 个CreateWindowEx接受Unicode字符串,另一个CreateWindowEx则接受ANSI字符串。这没错, 但两个函数的原型实际是这样的: 

HWND WINAPI CreateWindowExW (   DWORD dwExStyle,   PCWSTR pClassName,  // A Unicode string   PCWSTR pWindowName, // A Unicode string   DWORD dwStyle,   int X,   int Y,   int nWidth,   int nHeight,   HWND hWndParent,   HMENU hMenu,   HINSTANCE hInstance,   PVOID pParam); HWND WINAPI CreateWindowExA (   DWORD dwExStyle,   PCSTR pClassName,  // An ANSI string   PCSTR pWindowName, // An ANSI string   DWORD dwStyle,   int X,   int Y,   int nWidth,   int nHeight,   HWND hWndParent,   HMENU hMenu,   HINSTANCE hInstance,   PVOID pParam); 

CreateWindowExW这个版本接受Unicode字符串。函数名末尾的大写字母W代表wide 。 Unicode字符都是16位宽,所以它们常常被称作宽(wide )字符。CreateWindowExA 末尾 的大写字母A表明该函数接受ANSI字符串。 但在平时,我们只是在自己的代码中调用CreateWindowEx,不会直接调用 CreateWindowExW或CreateWindowExA 。在WinUser.h 中,CreateWindowEx实际是一个 宏,它的定义如下: 

#ifdef UNICODE 

#define CreateWindowEx CreateWindowExW 

#else

#define CreateWindowEx CreateWindowExA 

#endif 

编译源代码模块时,是否定义UNICODE决定了要调用哪一个版本的CreateWindowEx 。用 Visual Studio创建一个新项目的时候,它默认会定义UNICODE 。所以,在默认情况下,对 

CreateWindowEx 的任何调用都会扩展宏来调用CreateWindowExW——即Unicode版本的 

CreateWindowEx 。 

在Windows Vista 中,CreateWindowExA 的源代码只是一个转换层(translation layer ),它负 责分配内存,以便将ANSI字符串转换为Unicode字符串;然后,代码会调用 CreateWindowExW,并向它传递转换后的字符串。CreateWindowExW返回时, CreateWindowExA会释放它的内存缓冲区,并将窗口句柄返回给你。所以,对于要在缓冲 区中填充字符串的任何函数,在你的应用程序能够处理字符串之前,系统必须先将Unicode 转换为非Unicode 的等价物。由于系统必须执行所有这些转换,所以应用程序需要更多内存, 

而且运行速度较慢。为了提高使应用程序的执行更高效,你应该一开始就用Unicode来开发 程序。另外,目前已知Windows 的这些转换函数中存在一些缺陷,所以避免使用它们,还有 

助于消除一些潜在的bug 。 如果是在创建供其他软件开发人员使用的动态链接库(dynamic-link library,DLL ),可考 虑使用这种技术:在DLL 中提供导出的两个函数,一个ANSI版本的,一个Unicode版本的。 在ANSI版本中,只是分配内存,执行必要的字符串转换,然后调用该函数的Unicode版本。 

Windows API 中的一些函数(比如WinExec和OpenFile )存在的惟一目的就是提供与只支持 ANSI字符串的16位Windows程序的向后兼容性。在新程序中,应避免使用这些方法。在使 用WinExec和OpenFile调用的地方,应该用CreateProcess和CreateFile函数调用来代替。在 内部,老函数总是会调用新函数。但老函数的最大问题在于,它们不接受Unicode字符串, 而且支持的功能一般都要少一些。调用这些函数的时候,必须向其传递ANSI字符串。在 Windows Vista 中,大部分尚未废弃的函数都有Unicode和ANSI两个版本。然而,Microsoft 逐渐开始倾向于某些函数只提供Unicode版本,比如ReadDirectoryChangesW和 CreateProcessWithLogonW 。 Microsoft将COM从16位Windows移植到Win32 时,做出了一个重要决策:所有需要字符串作 为参数的COM接口方法都只接受Unicode字符串。这是一个伟大的决策,因为COM一般用于 让不同的组件彼此间进行“对话”,而Unicode是传递字符串最理想的选择。在你的应用程序中全面使用Unicode ,可以使它与COM 的交互变得更容易。 最后,当资源编译器编译完所有资源后,输出文件就是资源的一个二进制形式。资源中的字 符串值(字符串表、对话框模板、菜单等等)始终都写成Unicode字符串。在Windows Vista 中,如果你的应用程序没有定义UNICODE宏,操作系统将执行内部转换。例如,在编译源 模块时,如果没有定义UNICODE ,那么对LoadString 的调用实际会调用LoadStringA函数。 然LoadStringA读取资源中的Unicode字符串,并把它转换成ANSI形式。最后,转换为 ANSI形式的字符串将从函数返回到应用程序。


 C 运行库中的 Unicode 函数和ANSI 函数 

和Windows 函数一样,C运行库提供了一系列函数来处理ANSI字符和字符串,并提供了另一 系列函数来处理Unicode字符与字符串。然而,与Windows不同的是,ANSI版本的函数是“自 力更生”的:它们不会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本。当 然,Unicode版本的函数也是“自力更生”的,它们不会在内部调用ANSI版本。 在C运行库中,能返回ANSI字符串长度的一个函数的例子是strlen 。与之对应的是wcslen , 这个C运行库函数能返回Unicode字符串的长度。 这两个函数的原型都在String.h中。为了使你的源代码针对ANSI或Unicode都能编译,那么还 必须包含TChar.h,该文件定义了以下宏: 

#ifdef _UNICODE 

#define _tcslen wcslen 

#else 

#define _tcslen strlen

#endif 

现在,在你的代码中应该调用_tcslen 。如果已经定义了_UNICODE ,它会扩展为wcslen ;否 则,它会扩展为strlen 。默认情况下,在Visual Studio 中新建一个C++项目时,已经定义了 _UNICODE (就像已经定义了UNICODE一样)。针对不属于C++标准一部分的标识符,C 运行库始终为其附加下划线前缀。但是,Windows 团队没有这样做。所以,在你的应用程序 中,应确保要么同时定义了UNICODE和_UNICODE ,要么一个都不要定义。附录A将详细 描述CmnHdr.h ;本书所有示例代码都将用这个头文件来避免这种问题。  ANSI操作函数以str开头,如strcpy(),strcat(),strlen();Unicode操作函数以wcs开头,如wcscpy,wcscpy(),wcslen();ANSI/Unicode操作函数以_tcs开头 _tcscpy(C运行期库);ANSI/Unicode操作函数以lstr开头 lstrcpy(Windows函数);考虑ANSI和Unicode的兼容,我们需要使用以_tcs开头或lstr开头的通用字符串操作函数。


四 为何要用 Unicode 
开发应用程序的时候,强烈建议你使用Unicode字符和字符串。下面是一些理由。 Unicode使程序的本地化变得更容易。 使用Unicode ,只需发布一个二进制(.exe或DLL )文件,即可支持所有语言。 Unicode提升了应用程序的效率,因为代码执行速度更快,占用内存更少。Windows 内部的一切工作都是使用Unicode字符和字符串来进行的。所以,假如你非要传 入ANSI字符或字符串,Windows就会被迫分配内存,并将ANSI字符或字符串转 换为等价的Unicode形式。 使用Unicode ,你的应用程序能轻松调用所有不反对使用(nondeprecated )的Windows 函数,因为一些Windows 函数提供了只能处理Unicode字符和字符串的版本。 使用Unicode ,你的代码很容易与COM集成(后者要求使用Unicode字符和字符串)。 使用Unicode ,你的代码很容易与.NET Framework集成(后者要要求使用Unicode 字符和字符串)。 使用Unicode ,能保证你的代码能够轻松操纵你自己的资源(其中的字符串总是 Unicode 的)

 
五 推荐的字符和字符串处理方式 
基于本章到目前为止的内容,本节首先要总结开发代码时始终要牢记的几点。接下来要提 供一些提示与技巧,帮你更好地处理Unicode和ANSI字符串。你最好现在就将应用程序转 换为支持Unicode 的形式,即使并不计划立即开始使用Unicode字符。下面是一些应该遵循 的基本准则: 开始将文本字符串想象为字符的数组,而不是char或字节的数组。 为文本字符和字符串使用泛型(比如TCHAR/PTSTR )。 为字节、字节指针和数据缓冲区使用显式数据类型(BYTE和PBYTE ) 。  为literal字符和字符串使用TEXT或_T宏,但为了保持一致性和更好的可读性,请 避免两者混用。 执行全局替换。(例如,用PTSTR替换PSTR )。 修改字符串算术问题。例如,函数经常希望你传给它缓冲区的字符数,而不是字节 数。这意味着你应该传入_countof(szBuffer) ,而不是sizeof(szBuffer) 。而且,如 果需要为一个字符串分配一个内存块,而且知道字符串中的字符数,那么记住内 存是以字节来分配的。这意味着你必须调用malloc(nCharacters * sizeof(TCHAR)),而不是调用malloc(nCharacters) 。在前面列出的所有基本准则 中,这是最难记住的一条,而且如果出错,编译器不会提供任何警告或错误信息。 所以,最好定义一个宏来避免犯错: #define chmalloc(nCharacters) (TCHAR*)malloc(nCharacters * sizeof(TCHAR)). 避免使用printf系的函数,尤其是不要用%s和%S字段类型来进行ANSI与字符串的 相互转换。正确的做法是使用 MultiByteToWideChar和WideCharToMultiByte 函数,详情参见后面的“Unicode与ANSI字符串转换”一节。 UNICODE和_UNICODE符号要么都指定,要么一个都不指定。 对于字符串处理函数,应该遵循以下基本准则: 始终使用安全的字符串处理函数,比如那些后缀为_s 的,或者前缀为StringCch的。 后者主要在你想明确控制截断的时候使用;如果不想明确控制截断,则首选前者。 不要使用不
安全的C运行库字符串处理函数(参见前面的建议)。一般情况下,你 使用或实现的任何缓冲区处理例程都必须获取目标缓冲区的长度作为一个参数。 C运行库提供了一系列缓冲区处理替代函数,比如memcpy_s,memmove_s,  wmemcpy_s或wmemmove_s 。只要定义了__STDC_WANT_SECURE_LIB__符 号,所有这些方法都是可用的;CrtDefs.h默认定义了此符号。所以,不要对 _STDC_WANT_SECURE_LIB__进行undef 。 利用/GS (http://msdn2.microsoft.com/en-us/library/aa290051(VS.71).aspx)和/RTCs编 译器标志来自动检测缓冲区溢出。 不要用Kernel32方法来进行字符串处理,比如lstrcat和lstrcpy 。 在我们的代码中,需要要比较两种字符串。其中,编程类的字符串包括文件名、路 径、XML元素/属性以及注册表项/值等等。对于这些字符串,应使用 CompareStringOrdinal来进行比较。因为它非常快,而且不会考虑用户的区域 设置。这是完全合理的,因为不管程序在世界上的什么地方运行,这种字符串都 是不变的。用户字符串则一般要在用户界面上显示。对于这些字符串,应使用 CompareString(Ex)来比较,因为在比较字符串的时候,它会考虑用户的区域设 置。  你别无选择:作为专业开发人员,基于不安全的缓冲区处理函数来写代码是不允许的。 正是这个原因,本书所有代码都是使用C运行库中的这些更安全的函数来写的。 一些字符串操作函数需要获取字符串的字符数(sizeof(szBuffer)/sizeof(TCHAR)),而另一些函数可能需要获取字符串的字节数 sizeof(szBuffer)。您应该注意该问题并仔细分析字符串操作函数,以确定能够得到正确的结果。
ANSI操作函数以str开头,如strcpy(),strcat(),strlen();
Unicode操作函数以wcs开头,如wcscpy,wcscpy(),wcslen();
ANSI/Unicode操作函数以_tcs开头 _tcscpy(C运行期库);
ANSI/Unicode操作函数以lstr开头 lstrcpy(Windows函数);
考虑ANSI和Unicode的兼容,我们需要使用以_tcs开头或lstr开头的通用字符串操作函数。


六 Unicode 与ANSI 字符串转换
我们使用Windows 函数MultiByteToWideChar将多字节字符串转换为宽字符串。如下所 示: int MultiByteToWideChar(  UINT uCodePage,  DWORD dwFlags,  PCSTR pMultiByteStr,  int cbMultiByte,  PWSTR pWideCharStr,  int cchWideChar); uCodePage参数标识了与多字节字符串关联的一个代码页值。dwFlags参数允许你进行额 外的控制,它会影响使用了读音符号(比如重音)的字符。但是,一般情况下都不使用这 些标志,所以为dwFlags参数传入的是0值(要想更多地了解这个标志的值,请阅读MSDN 联机帮助,网址是http://msdn2.microsoft.com/en-us/library/ms776413.aspx )。pMultiByteStr 参数指定要转换的字符串,cbMultiByte参数指定字符串的长度(字节数)。如果为 cbMultiByte参数传入-1的话,函数会自动判断源字符串的长度。 转换所得的Unicode版本的字符串被写入pWideCharStr参数所指定地址的内存缓冲区中。 必须在cchWideChar参数中指定这个缓冲区的最大长度(字符数)。如果调用 MultiByteToWideChar ,并为cchWideChar参数传入0,函数就不会执行转换,而是返回 为了成功转换,缓冲区必须提供的宽字符数(包括终止字符'\0' )。一般按照以下步骤将 一个由多字节字符串转换为Unicode形式:

1. 调用MultiByteToWideChar ,为pWideCharStr参数传入NULL ,为cchWideChar参数 传入0,为cbMultiByte参数传入-1 。 

2. 分配足以容纳转换后的Unicode字符串的一个内存块。它的大小是上一个 MultiByteToWideChar调用的返回值乘以sizeof(wchar_t) 。 

3.  再次调用MultiByteToWideChar ,这一次将缓冲区地址作为pWideCharStr参数的值 传入,将第一次MultiByteToWideChar调用的返回值乘以sizeof(wchar_t)后得到的 大小作为cchWideChar参数的值传入
4. 使用转换后的字符串。 

5. 释放Unicode字符串占用的内存块。 对应地,WideCharToMultiByte 函数将宽字符字符串转换为多字节字符串,如下示: 

int WideCharToMultiByte(  UINT uCodePage,  DWORD dwFlags,  PCWSTR pWideCharStr,  int cchWideChar, PSTR pMultiByteStr,  int cbMultiByte,  PCSTR pDefaultChar,  PBOOL pfUsedDefaultChar); 这个函数类似于MultiByteToWideChar 函数。同样地,uCodePage标识了要与新转换的字 符串关联的代码页。dwFlags参数允许你指定额外的转换控制。这些标志会影响有有读音 符号的字符,以及系统不能转换的字符但一般都不需要进行这种程度的转换控制,因而 为dwFlags参数传入0 。 pWideCharStr参数指定要转换的字符串的内存地址,cchWideChar参数指出该字符串的 长度(字符数)。如果为cchWideChar参数传入-1,则由函数来判断源字符串的长度。 转换所得的多字节版本的字符串被写入pMultiByteStr参数所指定的缓冲区。必须在 cbMultiByte参数中指定此缓冲区的最大大小(字节数)。调用WideCharToMultiByte 函 数时,如果将0作为cbMultiByte参数的值传入,会导致该函数返回目标缓冲区需要的大小。 将宽字符串转换为多字节字符串时,采取的步骤和前面将多字节字符串转换为宽字符串的 步骤相似;唯一不同的是,返回值直接就是确保转换成功所需的字节数,所以无需执行乘 法运算。 注意,与MultiByteToWideChar 函数相比,WideCharToMultiByte 函数接受的参数要多两 个,分别是pDefaultChar和pfUsedDefaultChar 。只有一个字符在uCodePage指定的代码 页中无表示时,WideCharToMultiByte 函数才会使用这两个参数。遇到一个不能转换的宽 字符,函数便会使用pDefaultChar参数指向的字符。如果这个参数为NULL(这是很常见 的一个情况),函数就会使用一个系统默认的字符。这个默认字符通常是一个问号。这对 文件名来说非常危险,因为问号是一个通配符。 pfUsedDefaultChar参数指向一个布尔变量;在宽字符字符串中,如果至少有一个字符不 能转换为其多字节形式,函数就会把这个变量设为TRUE 。如果所有字符都能成功转换, 就会把这个变量设为FALSE 。可以在函数返回后测试该变量,验证宽字符字符串是否已 成功转换。同样地,通常为此参数传入NULL值。

 
七 使用ANSI/Unicode通用数据类型
微软提供了一些ANSI和Unicode兼容的通用数据类型,我们最常用的数据类型有_T ,TCHAR,LPTSTR,LPCTSTR。顺便说一下,LPCTSTR和const TCHAR*是完全等同的。其中L表示long指针,这是为了兼容Windows 3.1等16位操作系统遗留下来的,在Win32 中以及其它的32位操作系统中,long指针和near指针及far修饰符都是为了兼容的作用,没有实际意义。P(pointer)表示这是一个指针;C(const)表示是一个常量;T(_T宏)表示兼容ANSI和Unicode,STR(string)表示这个变量是一个字符串。
综上可以看 出,LPCTSTR表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。
1.TCHAR* szText=_T(“Hello World!”);
2.TCHAR szText[]=_T(“Hello World”);
3.LPCTSTR lpszText=_T(“中国人!”);


八 使用VC++ 6.0进行Unicode编程主要做以下几项工作:
1、为工程添加UNICODE和_UNICODE预处理选项。
在没有定义UNICODE和_UNICODE时,所有函数和类型都默认使用ANSI的版本;在定义了UNICODE和_UNICODE之后,所有的MFC类和Windows API都变成了宽字节版本了。


2、设置程序入口点
因为MFC应用程序有针对Unicode专用的程序入口点,我们要设置entry point。否则就会出现连接错误。
设置entry point的方法是:打开[工程]->[设置…]对话框,在Link页的Output类别的Entry Point里填上
wWinMainCRTStartup。


九  举个Unicode编程的例子
第一步:
打开VC++6.0,新建基于对话框的工程
主对话框IDD_UNICODE_DIALOG中加入一个按钮控件,双击该控件并添加该控件的响应函数:
1.void CUnicodeDlg::OnButton1()
2.{
3.TCHAR* str1=_T("ANSI和UNICODE编码试验");
4.m_disp=str1;
5.UpdateData(FALSE);
6.}
第一步
添加静态编辑框IDC_STRSHOW,使用ClassWizard给该控件添加CString类型变量m_StrDisp。使用默认ANSI编码环境编译该工
程,生成Unicode.exe。
第二步:

改变操作系统语言测试。或者虚拟机安装日文操作系统测试


打开“控制面板”,单击“日期、时间、语言和区域设置”选项,在“日期、时间、语言和区域设置”窗口中继续单击“区域和语言选项”选项,弹出“区域和语言 选项”对话框。在该对话框中,单击“高级”标签,将“非Unicode的程序的语言”选项改为“日语”,单击“应用”按钮.弹出的对话框单击“是”,重新启动计算机使设置生效。
运行Unicode.exe程序并单击“编码测试”按钮,看,静态文本框出现了乱码。



第三步:
改为Unicode编码环境编译该工程,生成Unicode.exe。再次运行Unicode.exe.


看见了吧。很明显,UNICODE编码不会出现乱码,而且可以支持多平台语言系统。

建议以后大家编程的时候尽量使用UNICODE编码。