Windows 核心编程笔记 [2] 字符串
1. ANSI 和 Unicode
Windows 中涉及字符串的函数有两个版本
1)ANSI版本的函数会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本
2)Unicode版本会在内部调用ANSI版本
例:关于CreateFile有两个版本
CreateFileW 接受Unicode字符串
#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif
WINBASEAPI
HANDLE
WINAPI
CreateFileW(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
CreateFileA 接受ANSI字符串:内部转为Unicode,CreateFileA->CreateFileW
WINBASEAPI
HANDLE
WINAPI
CreateFileA(
_In_ LPCSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
2. 安全字符串函数
若目标字符串缓冲区不够大,无法容纳新生成的字符串,就会导致内存中的数据被破坏(memory corruption)
例:
strcpy 和 wcscpy函数(以及其他大多数字符串处理函数)
问题:没有收到指定了的缓冲区最大长度的参数,即函数不知道自己会出错,所以也不报错
1)新的安全字符串函数
①包含StrSafe.h时,string.h也会被包括进来。C运行库中现有的字符串处理函数已被标记为废弃不用,如果使用这些函数,编译时就会发出警告
②现有的每一个函数都有一个对应的新版本函数,末尾加上_s后缀(s代表source)
③将一个可写的缓冲区作为参数传递时,必须提供大小,可以对缓冲区使用_countof宏,很容易计算
例:
#define _tcscat wcscat
#define _tcscat_s wcscat_s
#define _tcscpy wcscpy
#define _tcscpy_s wcscpy_s
④所有安全函数的首要任务:验证传入的参数值(如以下源码的几个判断)
// wcscpy_s函数的源码:
INT CDECL wcscpy_s(wchar_t* wcDest, size_t numElement, const wchar_t *wcSrc)//characters
{
size_t size = 0;
if (!wcDest || !numElement)
return EINVAL;
wcDest[0] = 0;
if (!wcSrc)
{
return EINVAL;
}
size = strlenW(wcSrc) + 1;//所以传入的字符数不用包括'\0',此处已经+1
if (size > numElement)
{
return ERANGE;
}
memcpy(wcDest, wcSrc, size * sizeof(WCHAR));//bytes
return 0;
}
2)处理字符串时获得更多控制:格式化函数
_stprintf(BufferData, _T("我们都是中国人 %d %f"), v1,v2);
_stscanf(BufferData, _T("%s %d %f"),v5,&v3,&v4);
3)Windows字符串函数
字符串比较函数CompareString(Ex)和CompareStringOrdinal
CompareString(Ex)函数:需要以符合用户语言习惯的方式向用户显示的字符串
int CompareString (
LCID locale,
DWORD dwCmdFlags,
PCTSTR pString1,
int cch1,
PCTSTR pString2,
int cch2);
CompareStringOrdinal函数:比较内部所用的字符串(如路径名、注册表项/值1、XML元素属性等)
int CompareStringOrdinal (
PCWSTR pstring1,
int cchCount1,
PCWSTR pString2,
int cchCount2 ,
BOOL bIgnoreCase) ;
3. 支持单双字的方式(自己宏定义)
1)string类的单双字
wstring为支持双字的string类
string为支持单字的string类
#ifdef _UNICODE
#define _tstring wstring
#else
#define _tstring string
#endif
2)输入输出的单双字
wcout和wcin支持双字
cout和cin支持单字
#ifdef _UNICODE
#define _tcout wcout
#define _cin wcin
#else
#define _tcout cout
#define _cin cin
#endif
4. 1-3 部分完整的测试代码
#include <windows.h>
#include <iostream>
#include <tchar.h>
#include <atlstr.h>
using namespace std;
void Sub_1();
void Sub_2();
void Sub_3();
void Sub_4();
void Sub_5();
void _tmain()
{
_tsetlocale(LC_ALL, _T("Chinese-simplified"));
//Sub_1();
//Sub_2();
//Sub_3();
//Sub_4();
//Sub_5();
}
//字符串拷贝
void Sub_1()
{
TCHAR v1[20] = _T("HelloWorld"); //20 20 20 40
TCHAR v2[20] = {0};
//NumberOfCharacters
_tcscpy_s(v2, 11, v1); //第一参数为目标地址,第二参数为字符的数量,第三参数为源地址
_tprintf(_T("%s\r\n"), v2);
//Count Bytes Of Source to Destination
//memcpy(v2, v1, sizeof(TCHAR)*(10+1)); //第一参数为目标地址,第二参数为源地址,第三参数为字节大小
//_tprintf(_T("%s\r\n"), v2);
}
//sizeof和countf的区别
void Sub_2()
{
int v1[5] = { 1,2,3,4,5 };
TCHAR v2[] = _T("HellWorld");
//#define _countof(array) (sizeof(array)/sizeof(array[0])) sizeof(v1)/sizeof(v1[0])
_tprintf(_T("%d %d\r\n"), sizeof(v1), _countof(v1)); //结果为20,5
_tprintf(_T("%d %d\r\n"), sizeof(v2), _countof(v2)); //结果为20,10
}
//宏定义一个支持单双字的_tcout
void Sub_3()
{
#ifdef _UNICODE
#define _tstring wstring
#define _tcout wcout
#define _cin wcin
#else
#define _tstring string
#define _tcout cout
#define _cin cin
#endif
//_tstring v1 = _T("HelloString");
//_tcout << v1.c_str() << endl;
//_tprintf(_T("%s\r\n"), v1.c_str());
CString v2 = _T("HelloCString"); //Cstring支持单双字
_tcout << v2.GetString() << endl;
}
//字符串拷贝函数_tcscpy和memcpy的区别,memcpy可以重叠拷贝(自身前面的数据向后拷贝)
void Sub_4()
{
//重叠拷贝
TCHAR v1[20] = _T("HelloWorld");
//_tcscpy_s(&v1[0], 10, &v1[1]);
_tcscpy_s(&v1[1], 10, &v1[0]); //程序崩溃
_tcscpy(&v1[1], &v1[0]);
memcpy(&v1[1], &v1[0],(_tcslen(v1)+1)*sizeof(TCHAR));
_tprintf(_T("%s\r\n"), v1);
}
//_stprintf和_stscanf 字符串格式化
void Sub_5()
{
int v1 = 345;
float v2 = 4.14;
TCHAR BufferData[0x1000] = { 0 };
_stprintf(BufferData, _T("我们都是中国人 %d %f"), v1,v2);
_tprintf(_T("_tprintf:%s\r\n"), BufferData); //输出打印
int v3 = 0;
float v4 = 0;
TCHAR v5[0x1000];
_stscanf(BufferData, _T("%s %d %f"),v5,&v3,&v4);
_tprintf(_T("%d\r\n"),v3);
_tprintf(_T("%f\r\n"), v4);
_tprintf(_T("%s\r\n"), v5);
}
5. 字符编码
5.1 UTF-16编码
每个Unicode字符都使用UTF-16编码,UTF的全称是Unicode Transformation Format(Unicode转换格式)。UTF-16将每个字符编码为2个字节(或者说16位)。
1)16位不足以表示某些语言的所有字符,此时,UTF-16支持使用代理(surrogate),后者是用32位(4个字节)来表示一个字符的一种方式。
2)只有少数应用程序需要表示这些语言中的字符,所以UTF-16在节省空间和简化编码这两个目标之间,提供了一个很好的折衷。
3)注意:
.NET Framework始终使用UTF-16来编码所有字符和字符串,如果需要在本机代码(native code)和托管代码(managed code)之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。
5.2 UTF-8编码
UTF-8将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编码为3个字节,一些字符编码为4个字节。
值在Ox0080以下的字符压缩为1个字节,这对美国使用的字符非常适合。0x0080和 0x07FF之间的字符转换为2个字节,这对欧洲和中东地区的语言非常适用。0x0800 以上的字符都转换为3个字节,适合东亚地区的语言。最后,代理对(surrogate pair)被写为4个字节。UTF-8在对值为0x0800及以上的大量字符进行编码的时候,不如UTF-16高效。
5.3 UTF-32编码
UTF-32将每个字符都编码为4个字节。
适用场景
如果打算遍历字符(任何语言中使用的字符),但又不想处理字节数不定的字符,这种编码方式非常有用。
例如,采用UTF-32编码方式,不需要关心代理(surrogate)的问题,每个字符都是4个字符。
从内存使用这个角度来看,UTF-32并不是一种高效的编码格式。这种编码格式一般在应用程序内部使用。
5.4 Unicode 字符集和字母表
6位代码 | 字符 | 16位代码 | 字母/书写符号 |
---|---|---|---|
0000-007F | ASCII | 0300-036F | 常见的变音符号 |
0080-00FF | 西欧语系字母 | 0400-04FF | 西里尔字母 |
0100-017F | 欧洲拉丁字母 | 0530-058F | 亚美尼亚文 |
0180-01FF | 扩充拉丁字母 | 0590-05FF | 希伯来文 |
0250-02AF | 标准音标 | 0600-06FF | 阿拉伯文 |
02B0-02FF | 进格修饰字母 | 0900-097F | 梵文字母 |
6. Unicode 与 ANSI 字符串转换
6.1 MultiByteToWideChar 函数 ANSI->UNICODE
将多字节字符串转换为宽字符字符串
int MultiByteToWidechar(
UINT uCodePage,
DWORD dwFlags,
PCSTR pMultiByteStr,
int cbMultiByte,
PWSTR pWideCharStr,
int cchWideChar) ;
6.2 WideCharToMultiByte 函数 UNICODE->AMSI
将宽字符字符串转换为多字节字符串
int WideCharToMultiByte(
UINT uCodePage,
DWORD dwFlags,
PCWSTR pWideCharStr,
int cchWideChar ,
PSTR pMultiByteStr,
int cbMultiByte,
PCSTR pDefaultChar ,
PBOOL pfUsedDefaultChar) ;
6.3 字符串转换函数(自己定义的)
6.3.1 返回值为 BOOL 参数为 char* 和 WCHAR*
BOOL SeAnsiToUnicode(char* MultiByteString,WCHAR** WideString)
{
DWORD WideStringLength;
if (!MultiByteString)
{
return FALSE;
}
WideStringLength = MultiByteToWideChar(CP_ACP, 0, MultiByteString, -1, NULL, 0);
*WideString = (WCHAR*)malloc(WideStringLength * sizeof(WCHAR));
if (*WideString == NULL)
{
return FALSE;
}
MultiByteToWideChar(CP_ACP, 0, MultiByteString, -1, *WideString, WideStringLength);
return TRUE;
}
BOOL SeUnicodeToAnsi(WCHAR* WideString, char** MultiByteString)
{
DWORD MultiByteStringLength;
if (!WideString)
{
return FALSE;
}
MultiByteStringLength = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK ,
WideString, -1, NULL, 0, NULL, NULL);
*MultiByteString = (char*)malloc(MultiByteStringLength * sizeof(CHAR));
if (*MultiByteString == NULL)
{
return FALSE;
}
WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK,
WideString, -1, *MultiByteString, MultiByteStringLength, NULL, NULL);
return TRUE;
}
6.3.2 返回值为 DWORD 参数为 LPCSTR 和 LPWSTR
DWORD
SeAnsiToUnicode(LPCSTR AnsiString,
LPWSTR *UnicodeString)
{
INT v7;
INT IsOk = 0;
v7 = strlen(AnsiString) + 1;
*UnicodeString = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, v7 * sizeof(WCHAR));
if (*UnicodeString)
{
IsOk = MultiByteToWideChar(CP_ACP,
0,
AnsiString,
-1,
*UnicodeString,
v7);
}
return IsOk;
}
DWORD
SeUnicodeToAnsi(LPCWSTR UnicodeString,
LPSTR *AnsiString)
{
INT v7;
INT IsOk = 0;
v7 = wcslen(UnicodeString) + 1;
*AnsiString = (LPSTR)HeapAlloc(GetProcessHeap(), 0, v7);
if (*AnsiString)
{
IsOk = WideCharToMultiByte(CP_ACP,
0,
UnicodeString,
-1,
*AnsiString,
v7,
NULL,
NULL);
}
return IsOk;
}
6.3.3 返回值为 String 参数为 LPCSTR 和 LPWSTR
wstring __SeAnsiToUnicode(const char *MultiByteString)
{
size_t v7 = strlen(MultiByteString);
wchar_t* v5 = new wchar_t[v7+1];
mbstowcs(v5, MultiByteString, v7);
v5[v7] = 0x00;
wstring Object(v5);
delete[] v5;
return Object;
}
string __SeUnicodeToAnsi(const wchar_t *WideString) {
size_t v7 = wcslen(WideString);
char* v5 = new char[v7+1];
wcstombs(v5, WideString, v7);
v5[v7] = 0;
string Object(v5);
delete[] v5;
return Object;
}
6.4 ANSI与Unicode字符串的初始化
VOID SeRtlInitAnsiString(IN OUT PANSI_STRING AnsiString,
IN char* SourceString)
{
#define MAX_USHORT 0xffff
SIZE_T v7;
if (SourceString)
{
v7 = strlen(SourceString);
if (v7 > (MAX_USHORT - sizeof(CHAR)))
{
v7 = MAX_USHORT - sizeof(CHAR);
}
AnsiString->Length = (USHORT)v7;
AnsiString->MaximumLength = (USHORT)v7 + sizeof(CHAR);
}
else
{
AnsiString->Length = 0;
AnsiString->MaximumLength = 0;
}
AnsiString->Buffer = (PCHAR)SourceString;
}
VOID SeRtlInitUnicodeString(
IN OUT PUNICODE_STRING UnicodeString,
IN PCWSTR SourceString)
{
#define MAX_USHORT 0xffff
SIZE_T v7;
CONST SIZE_T MaxSize = (MAX_USHORT & ~1) - sizeof(UNICODE_NULL); // an even number
if (SourceString)
{
v7 = wcslen(SourceString) * sizeof(WCHAR);
if (v7 > MaxSize)
v7 = MaxSize;
UnicodeString->Length = (USHORT)v7;
UnicodeString->MaximumLength = (USHORT)v7 + sizeof(UNICODE_NULL);
}
else
{
UnicodeString->Length = 0;
UnicodeString->MaximumLength = 0;
}
UnicodeString->Buffer = (PWCHAR)SourceString;
}
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef struct _ANSI_STRING {
USHORT Length;
USHORT MaximumLength;
PSTR Buffer;
} ANSI_STRING;
typedef ANSI_STRING *PANSI_STRING;