《windows核心编程系列》二谈谈ANSI和Unicode字符集 .
http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!!
第二章:字符和字符串处理
使用vc编程时项目--》属性--》常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集。一般情况下说Unicode都是指UTF-16。也就是说每个字符编码为两个字节。65535个字符可以表示世界上大部分的语言。为了软件使国际化大家再编程时应该使用unicode字符集。由于原来学过c语言,不习惯使用Unicode,为了省事而直接在配置属性里调为多字节字符集,这是个不好的习惯。C语言的字符串,以及对这些字符串操作的函数都是不安全的。很容易导致缓冲区溢出错误,有时这会导致很严重的后果。所以极力建议大家要使用Unicode字符串,对它们进行的操作要使用接下来将要介绍的安全字符串函数。除此之外这对于提高性能也有帮助,因为现在大部分的windowsAPI都是直接对Unicode字符串进行操作,你所传入的多字节字符串会先转换为Unicode后再拿来使用。直接使用Unicode省略了转换的步骤,当然具有更高的性能。
C语言定义字符时使用的char类型,表示一个8位ANSI字符。
在vc中,microsoft定义了一个内置的数据类型wchar_t,它表示一个16位的Unicode字符。它是通过下面的语句定义的:
typedef unsigned short wchar_t;
很明显它占16位。定义Unicode字符与ANSI类似。如:
wchar_t a=L'a';
wchar_t array[100]=L"Hello world!!!";
对于array数组,它的每个字符都占16位且是以16位的\0结尾。
字符和字符串前的L是一个宏。它通知编译器应该将其编译为Unicode字符串。当编译器将此字符串放入程序的数据段时,会使用UTF-16来编码每个字符。
刚开始接触windows开发的同学会对windows提供的各种内置类型不知所措。其实这是windows为了显示出自己的与众不同将原来大家熟悉的c语言的内置类型起个别名罢了。如
typedef char CHAR;
typedef wchar_t WCHAR;
typedef CHAR *PCHAR;
typedef CHAR *PSTR;
typedef COSNT CHAR *PCSTR;
typedef WCHAR *PWCHAR;
typedef WCHAR *PWSTR;
typedef COSNT WCHAR *PCWSTR;
其实使用哪种数据类型并不重要,关键是能保持一致性。不要混合使用。如果是windows程序员最好能与windows保持一致。这可以增强代码的可读性。
为什么编译器能根据我们的设置自动为我们选择字符集呢。原来在本章开始时当我们选择使用Unicode字符集时,编译器会为我们在源文件中定义Unicode。形如#define Unicode。如果选择使用多字节字符集时会将定义Unicode的语句注释掉。为什么仅仅使用一句话就可以有这么大改变呢。请接着往下看:
#define UNICODE
typedef WCHAR TCHAR,*PTCHAR , PTSTR;
typedef COSNT WCHAR *PCTSTR;
#define _TEXT(quote) L##quote
#else
typedef CHAR TCHAR ,*PTCHAR,PTSTR;
typedef CONST CHAR *PCTSTR;
#define _TEXT(quote) quote
#endif
#define TEXT(quote) _TEXT(quote)
原来编译器使用预编译宏来进行判断,如果定义了UNICODE,就会将WCHAR定义为TCHAR,在_TEXT,在TEXT的参数前加上L。否则将TCHAR定义为CHAR,TEXT,_TEXT的参数前没有添加任何东西。明白了这些我们就应该清楚如何写出兼容性强的代码,也就是说让我们的代码在Unicode或是多字节字符集下都可以成功运行。或许你应经看出来了,使用TCHAR和TEXT。如:
TCHAR c=TEXT('a');
TCHAR array[100]=TEXT("Hello world!");
编译器会根据我们的设置,编译成不同的代码。在Unicode环境下,由于定义了UNICODE上述宏经过展开后会生成以下代码:
WCHAR c=L'a';
WCHAR array=L"Hello world!";
而在ANSI字符集下会生成这样的代码:
CHAR c='a';
CHAR array="Hello world!";
能看懂上述宏定义很重要,因为在vc中我们使用的DEBUG和RELEASE模式就是使用上述方法轻松转换的。当我们选择DEBUG模式时编译器会在源代码中定义_DEBUG。RELEASE模式时会定义NDEBUG。预编译指令会对上述定义进行判断,根据定义的不同生成不同的代码。对上述宏不懂得同学可以找下介绍宏的资料来看,宏参数、宏展开什么的。
正如前面介绍的,现在的windows版本内部处理字符串都是使用Unicode字符集,调用windows函数时如果传入的是一个ANSI字符串,函数首先会执行转换操作,将ANSI字符串转换为Unicode字符串。
如果一个windows函数参数列表中存在字符串,它通常有两个版本。一个接受Unicode字符串另一个接受ANSI字符串。比如大家比较熟悉的MessageBox,它就有两个版本,一个是MessageBoxA,处理ANSI字符串另一个是MessageBoxW用以处理Unicode字符串。既然MessageBox有两个版本为什么平时我们使用时只需要使用MessageBox就行了。这个还是跟宏定义有关。在windows提供的头文件中有这样一句话:
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
有了上面的介绍是不是这句话很容易的就看出来了。对了,MessageBox并不是真正的函数,它仅仅是一个宏罢了。根据是否定义UNICODE决定在源文件中使用哪个函数。很多函数都存在这样的情况。这也是很多新手在逆向工程函数拦截时使用如MessageBox却一无所获的原因。
当我们使用ANSI字符集时,UNICODE不会被定义,真正使用的是MessageBoxA函数,该函数首先执行将ANSI字符串转换为Unicode,然后再调用MessageBoxW函数。W版本返回的字符串也要先转换成ANSI字符串才被能被使用。与直接使用W版本相比这当然性能更低。
Windows的这种做法很好的兼容了Unicode和ANSI。可以考虑在我们自己的程序中使用这种方法。比如导出dll时,可以考虑导出两个函数一个ASNI版本,一个Unicode版本。ANSI版本仅执行字符串转换操作之后调用Unicode版本的函数。
以上介绍的都是windows系统处理Unicode和ANSI字符集的方法。作为一门语言C语言也提供了自己处理它们的机制。C运行库也提供了不同的函数处理不同的字符集,但是与windows不同的是c运行库的ANSI版本的函数不会在内部调用Unicode版本的函数。
如strlen用于处理ANSI字符集,返回字符串长度,与之对应的Unicode版本为wcslen。(wide character set)。
C语言提供的对字符串进行修改的函数存在安全隐患,容易造成缓冲区溢出。轻则结果错误,重则系统崩溃。因为它们对目标缓冲区进行操作时没有收到指定缓冲区的最大长度的参数,函数并不知道自己会破坏内存。Microsoft提供了一些新的安全的函数来取代C运行库不安全的字符串处理函数。虽然我们已经习惯使用strcpy,printf,strcat等这些函数,但是我们还是去要忘记它们,转而使用windows提供的这些的新的更安全的函数。所有要使用安全的函数程序必须包含StrSafe.h头文件。现有的每一个函数,如strcpy,_tcscpy都有一个对应的安全新版本函数,strcpy_s,_tcscpy_s。前面的名称相同但最后添加了一个_s,代表更安全。如老版本的函数的区别是它们需要目标缓冲区的大小。注意不是字符串所占的字节数,而是字符数,无论是ANSI字符还是Unicode字符集都可以使用宏_countof来得到字符数(使用sizeof不行哦,它返回字节数)。如果目标缓冲区不足以容纳结果数据,函数就会设置局部于线程的C运行时变量errno。然后返回一个errno_t值来指出成功或失败。关于安全函数仅仅介绍这么多,一者《windows核心编程》中关于此介绍的很简单且晦涩难懂。二者平时我们在开发时很少能够用到,只要知道每个对应的c字符串操作函数都对应着一个安全函数,用时再查也不迟。
现在再也不能将字符认为仅仅占一个字节了,因为存在Unicode的字符。要表示字节可以使用BYTE。建议平时使用通用的数据类型如TCHAR,PTSTR定义变量。同时将字符或字符串用TEXT或_T包括起来。
涉及到ANSI和Unicode字符集的转换可以使用以下两个函数:
MultiByteToWideChar和WideCharToMultiByte。
判断字符串是Unicode还是ANSI字符集可以使用函数IsTextUnicode。
《windows核心编程系列》二谈谈ANSI和Unicode字符集 .的更多相关文章
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- 《windows核心编程系列》十八谈谈windows钩子
windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 《windows核心编程系列》二十一谈谈基址重定位和模块绑定
每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- 《Windows核心编程系列》十四谈谈默认堆和自定义堆
堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...
- 《windows核心编程系列》十六谈谈内存映射文件
内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...
- 《windows核心编程系列》四谈谈进程的建立和终止
第二部分:工作机理 第一章:进程 上一章介绍了内核对象,这一节开始就要不断接触各种内核对象了.首先要给大家介绍的是进程内核对象.进程大家都不陌生,它是资源和分配的基本单位,而进程内核对象就是与进程相关 ...
- 《Windows核心编程系列》十二谈谈Windows内存体系结构
Windows内存体系结构 理解Windows内存体系结构是每一个励志成为优秀的Windows程序员所必须的. 进程虚拟地址空间 每个进程都有自己的虚拟地址空间.对于32位操作系统来说,它的地址空间是 ...
随机推荐
- php array 排序 感悟
array 排序总体有这几个函数sort.rsort.asort.arsort.ksort.krsort.usort.uasort.uksort. 一开始我记来记去总是有点混乱,后来认真对比后终于清 ...
- Mybatis.net与MVC入门配置及联合查询动态SQL拼接和简单事务
第一次学习Mybatis.net,在博客园也找到好多资料,但是在配置成功之后也遇到了一些问题,尤其是在动态SQl拼接时候,这里把遇到的问题还有自己写的一个Demo贴出来,希望能帮到新手,有不适合的地方 ...
- What does cmd /C mean? [closed] 关于nodejs的子进程部分
之前一直很不明白为什么 child_process.spawn(command[, args][, options]) shell <Boolean> | <String> I ...
- Oracle EBS-SQL (PO-13):检查采购物料无一揽子协议价格.sql
Select msi.segment1 物料编码, msi.DESCRIPTION ...
- 工作无聊,闲来无事,自己学习 android入门
工作无聊,闲来无事,自己学习. 最近几天看了看有关android的UI设计,布局以及android有关控件的知识,算是进一步了解了 android的相关内容. 明天就是周末了,明天及后天,我打算开始学 ...
- QNDTU外壳及开发板
昨天从淘宝上淘来了个DTU外壳,翻出来之前的STM32开发板和GPRS模块开发板,今天准备复习一下开发板,把裸板跑起来. 晒一下装备: 两块开发板: 51n ...
- JS:函数多个参数默认值指定
函数有一个参数时,以往这样定义(参数为p1): function mfun(p1){ … } 当需要为p1设定一个默认值时 function mfun(p1){ if(p1===undefined) ...
- web站点监控脚本web_status_code,tomcat 80,oracle1521
1,完整的监控脚本如下 #!/bin/bash #web_status_code=`curl -o /dev/null -s -w "http_code:%{http_code}" ...
- 单链表反转(递归和非递归) (Java)
链表定义 class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } 非递归实现很简单,只需要遍历一遍链表,在遍历过 ...
- mac系统奔溃无法启动时,如何备份重要资料
虽然说苹果系统以稳定性获得高度好评,但是作为一名程序员,还是要考虑到系统奔溃的情况. 当遇到系统奔溃,无法启动,而我们还没有备份电脑里面的重要资料,这时候不用着急.可以用下面的 方法来拯救你的苹果电脑 ...