Windows的字符和字符串

Windows日益流行,本地化Windows应用程序将是一大问题,其中一个核心的问题就如何处理不同字符集的问题。

字符编码

  很久以来我们都在使用ANSI编码来表示一个字符串,它是一个“以0结尾的单字节字符数组”。但是单个字节只能表示256个符号,难以应付世界上众多语言。
  之后世界上出现了DBSC双字节字符集,一个字符串中的每个字符都由1个或2个字节组成。但是每次使用都要判断哪些是一个字节,哪些是两个字节。
  1988年出台的Unicode编码很好的解决了这个问题。我们有很多UTF(Unicode Transformation Format :Unicode转换格式)标准,可以提供使用:

  • UTF-8:UTF-8将一些字节编码为1字节,有些编码为2字节,有些编码为3字节,有些编码为4字节。值在0x0080(ASCII范围内)以下的字符压缩为1字节,很好的应对英文格式;0x0080~0x07FF之间的字符转换为2字节,适合欧洲和中东语言;0x0800以上的都用3字节,这适合东亚地区语言;代理对(surrogate pair)被写为4字节。UTF-8相当流行,但是对大量0x0800以上字符编码的适合效率不高。
  • UTF-16:UTF-16为每个字符编码为2个字节。世界上大量的语言,每个字符都可以用16位来表示,所以应用程序很容易辨认字符串并计算它的长度。遇到某些语言16位长度无法表示的时候,UTF-16支持代理(surrogate),用32位来表示字符。UTF-16在节省空间和简化代码之间做出了很好的折中。.NET Framework始终使用UTF-16编码,所以如果我们需要在本机代码和托管代码之间传递字符,使用UTF-16能改进性能减少消耗。
  • UTF-32:每个字符都编码为4字节。这种格式从内存使用角度来说并不高效,使用率不高,但是并非毫无益处。如果打算写一个算法来遍历字符但是不想处理字节数不定的字符,就可以使用UTF-32。

数据类型

C语言使用char数据类型来表示一个8位的ANSI字符。
在源代码中声明字符串时,C编译器会把我们字符串中的字符转换成由8位char数据类型构成的一个数组:

char c= 'A';

char szBuffer[100] = "A String";

为了支持Unicode字符,Microsoft的C/C++编译器定义了一个内建的数据类型wchar_t,它表示一个16位的UTF-16字符。声明wchar_t字符的方法如下:

wchar_t c = L'A';

wchar_t szBuffer[100] = L"A String";

大写字母L通知编译器该字符应当编译为一个Unicode字符串。

为了和C语言进行区分,Windows头文件WinNT.h中定义了以下类型:

typedef char CHAR;   //8bit字符
typedef wchar_t WCHAR;   //16bit字符

//指向8bit字符(串)的指针
typedef CHAR *PCHAR;   
typedef CHAR *PSTR;
typedef CONST CHAR *PCSTR;

//指向16bit字符(串)的指针
typedef WCHAR *PWCHAR;   
typedef WCHAR *PWSTR;
typedef CONST WCHAR *PCWSTR;

另外WinNT.h还定义了一个宏TEXT(),用这个宏自动适配到底使用ANSI还是Unicode。

Windows中的Unicode函数和ANSI函数

  目前Windows都用Unicode来构建。所有核心函数都需要Unicode字符串。调用函数如果传进的是ANSI字符串,系函数首先把字符串转为Unicode,如果希望返回ANSI,操作系统会把Unicode转为ANSI。执行这些操作会花费额外的时间和内存开销。
  Windows系统函数如果参数有字符串,那么通常这个函数有两个版本,如CreateWindowEx既可以接收ANSOI,也能接收Unicode。但是这个函数的原型如下:

HWND WINAPI CreateWindowExW(………………);  //W代表wide,接收16位Unicode
HWND WINAPI CreateWindowExA(………………);  //A代表ANSI,接收8位ANSI

虽然有两个原型,但是我们调用的时候不必调用这两个,只需要调用CreateWindowEx。WinUser.h中定义了如下宏:

#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif

  虽然有两个版本,但是实际开发中最好还是使用Unicode,可以避免额外开销和bug。

  如果是要开发DLL,可以在DLL中提供两个版本,其中ANSI版本的仅仅分配内存,执行字符串转换,然后调用该函数的Unicode版本。

  资源编辑器编译完所有资源后,输出文件就是一个二进制形式。资源中的所有字符串都是Unicode形式保存的。如果没有定义UNICODE宏,那么操作系统将执行内部转换。

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

  C运行库和Windows系统一样,也是一个函数提供两个版本。但是和Windows不同,C运行库中的ANSI函数不会把字符串转换为Unicode形式,再调用Unicode版本了。所有函数都是自力更生的。

  必然C运行库中的字符串长度函数,strlen返回一个ANSI字符串长度,wcslen返回Unicode字符串长度。

  上述两个函数都在Strlen.h中定义。为了既能用ANSI编译,也能用Unicode编译,还必须包含TChar.h,该文件有如下宏:

#ifdef _UNICODE
#define _tcslen    wcslen
#else
#define _tcslen    strlen
#endif

C运行库中的安全字符串函数

repel-attacks-with-visual-studio-2005-safe-c-and-c-libraries
StrSafe.h介绍
安全函数替代列表
  传统方式修改字符串会有安全隐患,如果目标字符串缓冲区不够大,就会导致内存中数据被破坏。类似与strcpy这种函数根本不知道缓冲区长度,函数自己不知道可能破坏内存,所以不会向程序报告错误。

  现在我们可以用安全函数来替代这些不安全的函数。(由于strlenwcslen_tcslen不修改字符串,所以这些函数并没有破坏内存的隐患,但是会有其他隐患:这些函数默认字符串以’\0’终止,但是实际情况却不一定)

  microsoft的头文件StrSafe.h文件中定义了所有的安全字符串函数。在应用程序包含StrSafe.h时,String.h也会包含进来。要注意必须再包含其他文件之后再包含StrSafe.h。

  现在的每一个字符串函数都有其新版本,前面名称相同,后面添加 _s(secure)。我们在将一个可写的缓冲区作为参数传递时,必须同时提供它的大小,这个值应该是一个字符数。我们对缓冲区用 _couontof宏(stdlib.h)可以很容易计算出这个值。

获取安全函数运行结果

  安全函数首要任务是验证传入的所有参数是否合法,如果有某一项检查结果失败,函数会设置局部于线程的C运行时变量 errno,并返回一个 errno_t值来指出成功或失败。当然这些系统的函数并不会实际返回。

  在调试版构建时(debug build),如果检查有错误,系统会弹出Debug Assertion Faild对话框,然后中止程序进行。在发行版构建(release build)中,不会有弹窗出现,直接退出程序。

  我们可以提供自己的函数,当检测到无效参数时调用我们的函数,进行我们自己的操作。我们必须要先定义好一个函数:

void InvalidParameterHander(
	PCTSTR expression,    //运行时可能出现的函数调用失败,如(L"Buffer is too small" && 0)
	PCTSTR function,	  //错误的函数名称
	PCTSTR file,          //源代码文件
	unsigned int line,    //源代码行号
	uintptr_t /*pReserved*/
);

  只有在测试调试版构建时才适合使用上述函数来记录错误,其余情况这些参数都应赋值为NULL。因为这些显示方式对用户并不友好。在发行版构建中应使用更加友好的方式来替换对话框。

  在定义好函数后,下一步是调用 _set_invalid_parameter_handler来注册这个处理程序。然后在应用程序开头调用 **_CrtSetReportMode(_CRT_ASSERT,0)**来禁止运行时触发Dubug Assertion Failed对话框。

  现在我们在使用安全函数的时候,就可以检查获取的返回值 errno_t来检查结果,所有结果在errno.h中有定义,只有返回S_OK才代表成功。

处理字符串时进行更多控制

  除了以 _s结尾的安全函数之外,C运行库还有其他函数来提供更多控制,如控制填充符,或者指定如何进行截断。C运行库也为这些函数准备了ANSI(A)版本和Unicode(W)版本。部分函数如下:

HRESULT StringCchCat(PTSTR pszDest,size_t cchDest,PCTSTR pszSrc);
HRESULT StringCchCatEx(PTSTR pszDest,size_t cchDest,PCTSTR pszSrc,
			PTSTR *ppszDestEnd,size_t *pcchRemaining,DWORD dwFlags);

这些函数中都有一个“Cch”,这表示Count of characters,即字符数,可以用 _countof宏来获取此值。另外除了Cch还有一系列函数中有“Cb”,这些函数要求使用字节数而不是字符数来指定大小,用sizeof操作符来获取此值。

这些函数返回的HRESULT取值如下:

HRESULT描述
S_OK成功。目标缓冲区包含源字符串并以’\0’结尾
STRSAFE_E_INVALID_PARAMETER失败,将NULL传给了一个参数
STRSAFE_E_INSUFFICIENT_BUFFER失败。指定目标缓冲区太小,无法容纳整个源字符串

不同与安全函数,缓冲区太小,最终字符串会截断为一个空字符串。这些函数在缓冲区太小时,源缓冲区中可以被写入的那一部分会被复制,最后一个可用字符被设置为’\0’。最终需要哪种结果需要我们自行判断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值