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;
posted @ 2023-03-04 20:51  修竹Kirakira  阅读(48)  评论(0编辑  收藏  举报