欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解C++中的ANSI與Unicode和UTF8三種字符編碼基本原理與相互轉(zhuǎn)換

 更新時(shí)間:2021年11月15日 10:45:49   作者:link-初揚(yáng)  
在C++編程中,我們有時(shí)需要去處理字符串編碼的相關(guān)問題,常見的字符編碼有ANSI窄字節(jié)編碼、Unicode寬字節(jié)編碼及UTF8可變長編碼。很多人在處理字符串編碼問題時(shí)都會(huì)有疑惑,即便是有多年工作經(jīng)驗(yàn)的朋友也可能搞不清楚。所以有必要講一下這三種字符編碼以及如何去使用它們

1、概述

在日常的軟件開發(fā)過程中,會(huì)時(shí)不時(shí)地去處理不同編碼格式的字符串,特別是在處理文件路徑的相關(guān)場景中,比如我們要通過路徑去讀寫文件、通過路徑去加載庫文件等。常見的字符編碼格式有ANSI窄字節(jié)編碼、Unicode寬字節(jié)編碼以及UTF8可變長編碼。在Linux系統(tǒng)中,主要使用UTF8編碼;在Windows系統(tǒng)中,既支持ANSI編碼,也支持Unicode編碼。

通用的大小寫字母和數(shù)字則使用全球統(tǒng)一的固定編碼,即ASCII碼。

ANSI編碼是各個(gè)國家不同語種下的字符編碼,其字符的編碼值只在該語種中有效,不是全球統(tǒng)一編碼的,比如中文的GB2312編碼就是簡體中文的ANSI編碼。

Unicode編碼則是全球統(tǒng)一的雙字節(jié)編碼,所有語種的字符在一起統(tǒng)一的編碼,每個(gè)字符的編碼都是全球唯一的。

UTF8編碼是一種可變長的寬字節(jié)編碼,也是一種全球統(tǒng)一的字符編碼。

本文將以WIndows中使用Visual Studio進(jìn)行C++編程時(shí)需要處理的字符編碼問題為切入點(diǎn),詳細(xì)講解一下字符編碼的相關(guān)內(nèi)容。

2、Visual Studio中的字符編碼

在Visual Studio中編寫C++代碼時(shí),該如何指定字符串的編碼呢?其實(shí)很簡單,使用雙引號(hào)括住的字符串,使用的就是ANSI窄字節(jié)編碼;使用L+雙引號(hào)括住的字符串,使用的就是Unicode寬字節(jié)編碼,如下所示:

char* pStr = "This is a Test.";    // ANSI編碼
WCHAR* pWStr = L"This is a Test."; // Unicode寬字節(jié)編碼

我們也可以使用_T宏定義來指定字符串的編碼格式:

TCHAR* pStr = _T("This is a Test.");

設(shè)置_T后,則由工程配置屬性中的字符集設(shè)置來確定到底是使用哪種編碼:

如果選擇多字節(jié)字符集,_T就被解釋為雙引號(hào),即使用ANSI窄字節(jié)編碼;如果選擇Unicode字符集,_T就被解釋為L,即使用Unicode寬字節(jié)編碼。

其實(shí),如果在工程配置中選擇使用Unicode字符集,工程中會(huì)添加一個(gè)_UNICODE宏,如下所示:

如果選擇多字節(jié)字符集,則沒有_UNICODE宏。代碼中正是通過這個(gè)宏來判定到底使用哪種編碼的,比如對_T的判斷:

#ifdef  _UNICODE
#define  _T(X)    L(X)
#else
#define  _T(X)    (X)
#endif  // _UNICODE

和字符編碼相對應(yīng)的,Windows系統(tǒng)提供兩個(gè)版本的API,比如給窗口設(shè)置文字的API函數(shù),一個(gè)是支持ANSI窄字節(jié)編碼的SetWindowTextA(ANSI窄字節(jié)版本),一個(gè)是支持Unicode寬字節(jié)編碼的SetWindowTextW(Wide寬字節(jié)版本)。我們也可以直接調(diào)用SetWindowText,然后由_UNICODE宏判斷到底使用哪個(gè)版本,如下:

#ifdef _UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif // !UNICODE

3、ANSI窄字節(jié)編碼

ANSI編碼是不同語種下的字符編碼,比如GB2312字符編碼就是簡體中文的本地編碼。

ANSI編碼是個(gè)本地范疇,只適用于對應(yīng)的語種,每個(gè)字符的編碼不是全球唯一的編碼,只在對應(yīng)的語種中有效。對于中文GB2312編碼的字符串,如果當(dāng)成英文的ANSI編碼來解析,則結(jié)果會(huì)是亂碼!

但是對于大小寫英文字母和數(shù)字的ANSI編碼,是字符ASCII碼,英文字母和數(shù)字的ACSII碼是全球統(tǒng)一的,比如大寫字母A的ASCII碼是65(十六進(jìn)制是41H),數(shù)字0的ASCII碼是48(十六進(jìn)制是30H)。所以在所有語種中,大小寫字母及數(shù)字的ANSI編碼,都是能識(shí)別的。不同語種下的本地文字字符,一般是不能相互識(shí)別的。

使用中文ANSI編碼的字符串開發(fā)的程序(代碼中使用的都是中文字符串,使用的是ANSI窄字節(jié)編碼),拿到俄文操作系統(tǒng)中可能顯示的都是亂碼,因?yàn)樵诙砦牡腁NSI編碼中只識(shí)別俄文的ANSI編碼出來的字符串,無法識(shí)別中文ANSI編碼的字符串。這里主要有兩類字符亂碼問題,一是UI界面上顯示的文字是亂碼;二是使用路徑去創(chuàng)建文件或訪問文件時(shí)會(huì)因?yàn)槁窂街械淖址莵y碼,導(dǎo)致文件創(chuàng)建或訪問失敗。

4、Unicode寬字節(jié)編碼

Unicode編碼是全球統(tǒng)一的字符編碼,每個(gè)語種下的每個(gè)字符的編碼值都是全球唯一的,即在Unicode編碼集中可以識(shí)別每個(gè)語種下的所有字符。所以為了實(shí)現(xiàn)軟件對多語種(多國語言)的支持,我們在開發(fā)軟件時(shí)要選擇Unicode字符編碼,使用Unicode編碼的字符串,調(diào)用Unicode版本的API。

系統(tǒng)在提供包含字符串參數(shù)的API時(shí),都會(huì)提供兩個(gè)版本,一個(gè)是ANSI版本的,一個(gè)是Unicode版本的,主要體現(xiàn)在對字符串編碼的處理上,比如SetWindowTextA(ANSI版本)和SetWindowTextW(Wide寬字節(jié)Unicode版本)。我們可以直接調(diào)用W版本API,但一般我們調(diào)用API時(shí),我們不指定調(diào)用哪個(gè)版本,是通過設(shè)置工程屬性中的編碼格式來確定使用哪個(gè)版本:

#ifdef _UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif // !UNICODE

具體情況已在上面的“Visual Studio中的字符編碼”章節(jié)中詳細(xì)講述,此處不再贅述。

在Unicode編碼中,每個(gè)字符都占兩個(gè)字節(jié)。對于大小寫字母和數(shù)字,當(dāng)他們出現(xiàn)在字符串中時(shí),對應(yīng)的內(nèi)存中存放的是它們的ASCII碼值,只占一個(gè)字節(jié),在Unicode 2字節(jié)編碼中,高位將填充0。

5、UTF8編碼

UTF8編碼是可變長字符編碼格式,是一種緊湊型存儲(chǔ)的編碼格式,也是一種寬字節(jié)的、全球統(tǒng)一的編碼格式。UTF8編碼中的所有字符,包括不同語種下面的字符,都是全球唯一編碼的,在所有的系統(tǒng)都能識(shí)別出來。

UTF8編碼中,能用一個(gè)字節(jié)存放的,就用一個(gè)字節(jié)存放,比如大小寫字母和數(shù)字,在字符串中存放的ASCII碼,只需要一個(gè)字節(jié)去存放就夠了。所以在UTF8編碼中,大小寫字母和數(shù)字只占一個(gè)字節(jié)。我們常用的中文字符,一個(gè)字符則占用3個(gè)字節(jié)。

UTF8編碼之所以稱之為可變長的,是因?yàn)槠涓鶕?jù)字符需要的實(shí)際存儲(chǔ)空間大小來編碼的,比如大小寫字母和數(shù)字的存儲(chǔ)只需要1個(gè)字節(jié)就夠了,所以它們只占一個(gè)字節(jié),而一個(gè)中文字符則占三個(gè)字節(jié)。

6、如何使用字符編碼

Windows系統(tǒng)主要使用Unicode編碼,Linux則使用UTF8編碼,后臺(tái)服務(wù)器一般使用的都是Linux系統(tǒng),而客戶端是運(yùn)行在Windows操作系統(tǒng)上的。一般客戶端與服務(wù)器交互的數(shù)據(jù)的字符串編碼統(tǒng)一使用全球統(tǒng)一編碼的UTF8編碼。

客戶端收到UTF8編碼的字符串后,需要將UTF8字符換轉(zhuǎn)換后顯示在界面上。如果客戶端使用的是Unicode編碼字符集,將UTF8編碼的字符串轉(zhuǎn)換成Unicode編碼的字符串后再顯示到界面上;如果客戶端使用的是多字節(jié)ANSI編碼,則需要再將Unicode編碼的字符串轉(zhuǎn)成ANSI編碼的字符串。

這里注意一下,UTF8編碼的字符串要轉(zhuǎn)成ANSI編碼的,不能直接將UTF8轉(zhuǎn)成ANSI,需要先將UTF8轉(zhuǎn)成Unicode,然后再將Unicode轉(zhuǎn)成ANSI。

為了實(shí)現(xiàn)軟件對多語種(多國語言)的支持,我們在開發(fā)Windows軟件時(shí)要選擇Unicode字符編碼,使用Unicode編碼的字符串,調(diào)用Unicode版本的API。

此外,對于一些開源的項(xiàng)目,提供的API接口中有字符串參數(shù)的,一般都明確指定字符串編碼為UTF8。因?yàn)橐话闱闆r下開源庫都支持跨平臺(tái),既支持Windows平臺(tái),也支持Linux平臺(tái),所以要選擇使用通用的、大家都是識(shí)別的UTF8編碼。

比如在輕便型數(shù)據(jù)庫sqlite開源庫中,用于打開數(shù)據(jù)庫文件的接口sqlite3_open,就明確指定使用UTF8編碼的字符串:

**
** ^URI hexadecimal escape sequences (%HH) are supported within the path and
** query components of a URI. A hexadecimal escape sequence consists of a
** percent sign - "%" - followed by exactly two hexadecimal digits 
** specifying an octet value. ^Before the path or query components of a
** URI filename are interpreted, they are encoded using UTF-8 and all 
** hexadecimal escape sequences replaced by a single byte containing the
** corresponding octet. If this process generates an invalid UTF-8 encoding,
** the results are undefined.
**
** <b>Note to Windows users:</b>  The encoding used for the filename argument
** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever
** codepage is currently defined.  Filenames containing international
** characters must be converted to UTF-8 prior to passing them into
** sqlite3_open() or sqlite3_open_v2().
**
** <b>Note to Windows Runtime users:</b>  The temporary directory must be set
** prior to calling sqlite3_open() or sqlite3_open_v2().  Otherwise, various
** features that require the use of temporary files may fail.
**
** See also: [sqlite3_temp_directory]
*/
SQLITE_API int sqlite3_open(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API int sqlite3_open16(
  const void *filename,   /* Database filename (UTF-16) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API int sqlite3_open_v2(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb,         /* OUT: SQLite db handle */
  int flags,              /* Flags */
  const char *zVfs        /* Name of VFS module to use */
);

對于使用Unicode編碼的Windows程序,代碼中使用的都是Unicode編碼的字符串,在調(diào)用sqlite3_open接口之前,需要將Unicode編碼的字符串轉(zhuǎn)成UTF8編碼的。如果收到開源庫中回調(diào)上來的UTF8編碼的字符串?dāng)?shù)據(jù),則需要將UTF8編碼的字符串轉(zhuǎn)成Unicode后,才能顯示到UI界面上,才能使用轉(zhuǎn)碼后的Unicode字符串去調(diào)用Windows系統(tǒng)API。

7、三種字符編碼之間的相互轉(zhuǎn)換(附源碼)

有朋友曾經(jīng)提出這樣的疑問,是不是我在Windows下把一個(gè)雙引號(hào)括起來的ANSI窄字節(jié)字符串賦值給WCHAR寬字節(jié)的指針:

WCHAR* pStr = "測試字符串";

字符串就能自動(dòng)轉(zhuǎn)換成Unicode寬字節(jié)?答案是否定的,這樣的賦值操作并不會(huì)做字符編碼轉(zhuǎn)換,右側(cè)的僅僅是字符串的首地址,作為地址,可以賦值給很多數(shù)據(jù)類型,比如int、void*、char*等等。

那可能有人會(huì)說,那為啥我在Unicode下,將一個(gè)ANSI編碼的字符串傳給MFC庫中的CString類對象時(shí)會(huì)自動(dòng)轉(zhuǎn)換成Unicode寬字符呢?這和上面的情況不一樣的,是因?yàn)镃String類重載了賦值操作符函數(shù),在函數(shù)內(nèi)部做了字符編碼的轉(zhuǎn)換,代碼如下:

const CUIString& CUIString::operator=(LPCSTR lpsz)
{
	int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
	AllocBeforeWrite(nSrcLen);
	_ANSIToUnicode(m_pchData, lpsz, nSrcLen+1);
	ReleaseBuffer();
	return *this;
}

一般情況下,是需要我們自己去編寫字符編碼轉(zhuǎn)換的代碼的。下面來看一下,我們在進(jìn)行Windows C++編程時(shí),需要調(diào)用哪些API接口實(shí)現(xiàn)上述三種編碼之間的轉(zhuǎn)換。

7.1、ANSI編碼與Unicode編碼之間的轉(zhuǎn)換

ANSI轉(zhuǎn)成Unicode的代碼如下:

/*=============================================================================
函 數(shù) 名: AnsiToUnicode
功    能: 實(shí)現(xiàn)將char型buffer(ANSI編碼)中的內(nèi)容安全地拷貝到指定的WChar型(Unicode編碼)的buffer中
參    數(shù): char*  pchSrc [in]          源字符串
          WCAHR* pchDest [out]        目標(biāo)buf    
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void AnsiToUnicode( const char* pchSrc, WCHAR* pchDest, int nDestLen )
{
	if ( pchSrc == NULL || pchDest == NULL )
	{
		return;
	}
 
	int nTmpLen = MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, NULL, 0);
	WCHAR* pWTemp = new WCHAR[nTmpLen + 1];
	memset(pWTemp, 0, (nTmpLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, pWTemp, nTmpLen + 1);
 
	UINT nLen = wcslen(pWTemp);
	if (nLen + 1 > (nDestLen / sizeof(WCHAR)))
	{
		wcsncpy(pchDest, pWTemp, nDestLen / sizeof(WCHAR) - 1);
		pchDest[nDestLen / sizeof(WCHAR) - 1] = 0;
	}
	else
	{
		wcscpy(pchDest, pWTemp);
	}
 
	delete []pWTemp;
}

Unicode轉(zhuǎn)成ANSI的代碼如下:

/*=============================================================================
函 數(shù) 名: UnicodeToAnsi
功    能: 實(shí)現(xiàn)將WCHAR型buffer(Unicode編碼)中的內(nèi)容安全地拷貝到指定的char型(ANSI編碼)的buffer中
參    數(shù): WCHAR*  pchSrc [in]         源字符串
		  char*   pchDest[out]        目標(biāo)buf
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void UnicodeToAnsi(const WCHAR* pchSrc, char* pchDest, int nDestLen )
{
	if ( pchDest == NULL || pchSrc == NULL )
	{
		return;
	}
 
	const WCHAR* pWStrSRc = pchSrc;
	int nTmplen = WideCharToMultiByte(CP_ACP, 0, pWStrSRc, -1, NULL, 0, NULL, NULL);
	char* pTemp = new char[nTmplen + 1];
	memset(pTemp, 0, nTmplen + 1);
	WideCharToMultiByte(CP_ACP, 0, pWStrSRc, -1, pTemp, nTmplen + 1, NULL, NULL);
 
	int nLen = strlen(pTemp);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pTemp, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pTemp;
}

7.2、UTF8編碼與Unicode編碼之間的轉(zhuǎn)換

UTF8轉(zhuǎn)成Unicode的代碼如下:

/*=============================================================================
函 數(shù) 名: Utf8ToUnicode
功    能: 實(shí)現(xiàn)將char型的buffer(utf8編碼)中的內(nèi)容安全地拷貝到指定的WCHAR型buffer(Unicode編碼)中
參    數(shù): char* pchSrc [in]          源字符串
          WCHAR* pchDest [out]     目標(biāo)buf		 
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void Utf8ToUnicode( const char* pchSrc, WCHAR* pchDest, int nDestLen )
{
	if ( pchSrc == NULL || pchDest == NULL )
	{
		return;
	}
 
	int nTmpLen = MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, NULL, 0);
	WCHAR* pWTemp = new WCHAR[nTmpLen + 1];
	memset(pWTemp, 0, (nTmpLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, pWTemp, nTmpLen + 1);
 
	UINT nLen = wcslen(pWTemp);
	if (nLen + 1 > (nDestLen / sizeof(WCHAR)))
	{
		wcsncpy(pchDest, pWTemp, nDestLen / sizeof(WCHAR) - 1);
		pchDest[nDestLen / sizeof(WCHAR) - 1] = 0;
	}
	else
	{
		wcscpy(pchDest, pWTemp);
	}
 
	delete []pWTemp;
}

Unicode轉(zhuǎn)成UTF8的代碼如下:

/*=============================================================================
函 數(shù) 名: UnicodeToUtf8
功    能: 實(shí)現(xiàn)將WCHAR型buffer(Unicode編碼)中的內(nèi)容安全地拷貝到指定的char型的buffer(utf8編碼)中
參    數(shù): WCAHR* pchSrc [in]          源字符串
          char* pchDest [out]     目標(biāo)buf	  
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void UnicodeToUtf8(const WCHAR* pchSrc, char* pchDest, int nDestLen );
{
	if ( pchDest == NULL || pchSrc == NULL )
	{
		return;
	}
 
	const WCHAR* pWStrSRc = pchSrc;
	int nTmplen = WideCharToMultiByte(CP_UTF8, 0, pWStrSRc, -1, NULL, 0, NULL, NULL);
	char* pTemp = new char[nTmplen + 1];
	memset(pTemp, 0, nTmplen + 1);
	WideCharToMultiByte(CP_UTF8, 0, pWStrSRc, -1, pTemp, nTmplen + 1, NULL, NULL);
 
	int nLen = strlen(pTemp);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pTemp, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pTemp;
}

7.3、ANSI編碼與UTF8編碼之間的轉(zhuǎn)換

ANSI與UTF8之間是不能直接轉(zhuǎn)換的,需要先轉(zhuǎn)成Unicode之后才能轉(zhuǎn)到目標(biāo)編碼。

ANSI轉(zhuǎn)成UTF8的代碼如下:

/*=============================================================================
函 數(shù) 名: AnsiToUtf8
功    能: 實(shí)現(xiàn)將char型buffer(ANSI編碼)中的內(nèi)容安全地拷貝到指定的char型的buffer(utf8編碼)中
參    數(shù): char* pchSrc [in]          源字符串
          char* pchDest [out]     目標(biāo)buf
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void AnsiToUtf8( const char* pchSrc, char* pchDest, int nDestLen ) 
{
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}
 
	// 先將ANSI轉(zhuǎn)成Unicode
	int nUnicodeBufLen = MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, NULL, 0);
	WCHAR* pUnicodeTmpBuf = new WCHAR[nUnicodeBufLen + 1];
	memset(pUnicodeTmpBuf, 0, (nUnicodeBufLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, pUnicodeTmpBuf, nUnicodeBufLen + 1);
 
	// 再將Unicode轉(zhuǎn)成utf8
	int nUtf8BufLen = WideCharToMultiByte(CP_UTF8, 0, pUnicodeTmpBuf, -1, NULL, 0, NULL, NULL);
	char* pUtf8TmpBuf = new char[nUtf8BufLen + 1];
	memset(pUtf8TmpBuf, 0, nUtf8BufLen + 1);
	WideCharToMultiByte(CP_UTF8, 0, pUnicodeTmpBuf, -1, pUtf8TmpBuf, nUtf8BufLen + 1, NULL, NULL);
 
	int nLen = strlen(pUtf8TmpBuf);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pUtf8TmpBuf, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pUtf8TmpBuf);
	}
 
	delete[]pUtf8TmpBuf;
	delete[]pUnicodeTmpBuf;
}

UTF8轉(zhuǎn)成ANSI的代碼如下:

/*=============================================================================
函 數(shù) 名: Utf8ToAnsi
功    能: 實(shí)現(xiàn)將char型buffer(utf8編碼)中的內(nèi)容安全地拷貝到指定的char型的buffer(ANSI編碼)中
參    數(shù): char* pchSrc [in]          源字符串
		  char* pchDest [out]     目標(biāo)buf
		  int nDestLen [in]           目標(biāo)buf長度(注意:以字節(jié)為單位,不是以字符個(gè)數(shù)為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void Utf8ToAnsi(const char* pchSrc, char* pchDest, int nDestLen)
{
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}
 
	// 先將utf8轉(zhuǎn)成Unicode
	int nUnicdeBufLen = MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, NULL, 0);
	WCHAR* pUnicodeTmpBuf = new WCHAR[nUnicdeBufLen + 1];
	memset(pUnicodeTmpBuf, 0, (nUnicdeBufLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, pUnicodeTmpBuf, nUnicdeBufLen + 1);
 
	// 再將Unicode轉(zhuǎn)成utf8
	int nAnsiBuflen = WideCharToMultiByte(CP_ACP, 0, pUnicodeTmpBuf, -1, NULL, 0, NULL, NULL);
	char* pAnsiTmpBuf = new char[nAnsiBuflen + 1];
	memset(pAnsiTmpBuf, 0, nAnsiBuflen + 1);
	WideCharToMultiByte(CP_ACP, 0, pUnicodeTmpBuf, -1, pAnsiTmpBuf, nAnsiBuflen + 1, NULL, NULL);
 
	int nLen = strlen(pAnsiTmpBuf);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pAnsiTmpBuf, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pAnsiTmpBuf;
	delete []pUnicodeTmpBuf;
}

8、Windows系統(tǒng)對使用ANSI窄字節(jié)字符編碼的程序的兼容

現(xiàn)在的Windows程序基本都用Unicode字符編碼了,工程屬性中將字符集都設(shè)置成了Unicode字符集,代碼中都使用Unicode編碼的字符串。但是還有一些老的程序使用的還是ANSI窄字節(jié)的字符。那這些老的程序如何才能在外文的操作系統(tǒng)中正常運(yùn)行呢?微軟提供了一種兼容這些老程序的辦法。

可以到Windows控制面板的區(qū)域語言設(shè)置中將非Unicode語言設(shè)置成程序中使用的字符語種即可,相關(guān)設(shè)置的操作步驟截圖如下:

在上圖中選擇程序中字符使用的語種即可。

下面我們來看看使用ANSI編碼的程序放到外文操作系統(tǒng)中運(yùn)行為什么會(huì)出現(xiàn)亂碼。假設(shè)將某程序中使用的是中文ANSI窄字節(jié)編碼的字符串,放到英文操作系統(tǒng)中運(yùn)行,默認(rèn)情況下,UI界面上會(huì)顯示亂碼。至于為什么會(huì)顯示亂碼,是因?yàn)橛⑽牟僮飨到y(tǒng)中默認(rèn)情況下設(shè)置的非Unicode語言是英語(美國):

這個(gè)非Unicode語言設(shè)置直接影響我們調(diào)用MultiByteToWideChar和WideCharToMultiByte接口中的CP_ACP標(biāo)記對應(yīng)的本地ANSI字符集編碼庫。在上面界面中如果將非Unicode語言設(shè)置成英語(美國),則使用英文的ANSI字符編碼庫;如果設(shè)置成中文簡體,則使用中文簡體的ANSI字符集編碼庫。

程序中調(diào)用API函數(shù)SetWindowTextA給程序中的窗口設(shè)置文字或標(biāo)題時(shí),傳入的字符串是ANSI窄字節(jié)編碼的,而SetWindowTextA函數(shù)內(nèi)部及底層的流程中會(huì)使用本地設(shè)置的ANSI字符集編碼庫將ANSI編碼的字符串轉(zhuǎn)成Unicode編碼的字符串后再設(shè)置到窗口中,最終界面上看到的文字是Unicode編碼的文字。所以在將中文字符轉(zhuǎn)換成Unicode時(shí),如果使用的是本地設(shè)置的英文字符集編碼進(jìn)行轉(zhuǎn)換,則會(huì)出現(xiàn)亂碼;如果使用中文簡體的字符集編碼進(jìn)行轉(zhuǎn)換,則能正常顯示。

所以,要讓使用中文ANSI編碼字符的程序能在英文操作系統(tǒng)中正常顯示并運(yùn)行,需要將英文操作系統(tǒng)中區(qū)域語言設(shè)置項(xiàng)中的“非Unicode程序的語言”設(shè)置成中文才行。

9、字符編碼導(dǎo)致程序啟動(dòng)失敗的案例

幾天前正好排查了一例因?yàn)樽址幋a導(dǎo)致的程序啟動(dòng)失敗的實(shí)例,在這里簡單的說一下。客戶將軟件安裝到一個(gè)包含中文字符的路徑中,點(diǎn)擊啟動(dòng)軟件沒反應(yīng),軟件始終啟動(dòng)不了,也沒有彈出什么報(bào)錯(cuò)的提示框。客戶于是向我們反饋了這個(gè)問題。

我們使用向日葵遠(yuǎn)程到客戶的機(jī)器上,經(jīng)對比發(fā)現(xiàn),如果我們將軟件安裝到默認(rèn)的C:\Program Files(X86)的英文路徑下,程序是能正常啟動(dòng)的,所以我們初步懷疑可能是字符編碼引起的問題。重新將軟件安裝到D盤包含中文字符的路徑后,我們用windbg啟動(dòng)軟件,剛啟動(dòng)windbg中就檢測到看異常,異常發(fā)生在加載主程序依賴庫的過程中。

啟動(dòng)軟件的exe主程序時(shí),會(huì)將該exe依賴的所有庫依次加載到進(jìn)程空間中,待所有的庫都加載起來后,才會(huì)將exe主程序模塊啟動(dòng)起來,才能看到軟件的主界面。

如果在加載庫時(shí)產(chǎn)生了異常,整個(gè)啟動(dòng)過程將被終止,軟件也就無法啟動(dòng)了。

異常發(fā)生在加載音視頻編解碼庫mediaproc.dll中,于是在windbg中輸入kn命令,查看異常時(shí)的函數(shù)調(diào)用堆棧(事先已經(jīng)取來了pdb符號(hào)文件)。調(diào)用堆棧顯示時(shí)崩潰在mediaproc.dll庫的DllMain函數(shù)中,加載dll庫時(shí)都會(huì)調(diào)用到該接口。

根據(jù)調(diào)用堆棧中顯示的代碼行號(hào),到編解碼庫的源代碼中查看,發(fā)現(xiàn)是崩潰在一個(gè)函數(shù)接口指針的調(diào)用上,有可能是遇到空指針了。一般情況下,使用windbg實(shí)時(shí)調(diào)試時(shí)是能看到函數(shù)中的局部變量及類對象內(nèi)存中的值,但這次有點(diǎn)特殊,看不到內(nèi)存中的值。

于是和負(fù)責(zé)維護(hù)音視頻編解碼庫的同事溝通了一下, 編解碼庫mediaproc.dll在DllMain中會(huì)使用絕對路徑(當(dāng)前exe主程序的路徑)去調(diào)用LoadLibrary去動(dòng)態(tài)加載更底層的庫,然后調(diào)用GetProcAddress把底層庫的接口都拿出來保存到指針變量中。編解碼庫mediaproc.dll是調(diào)用ANSI版本的API函數(shù)GetModuleFileNameA獲取exe主程序的路徑,問題就出在這個(gè)函數(shù)的調(diào)用上,這個(gè)函數(shù)獲取的路徑中包含亂碼。

D盤包含中文字符的文件夾在系統(tǒng)中是能正常顯示的,為啥獲取的路徑中會(huì)包含亂碼呢?于是查看了客戶Windows操作系統(tǒng)版本,是Windows10 IOT版本,經(jīng)常見到旗艦版、專業(yè)版和教育版,這個(gè)IOT版本還是第一次遇到!于是又去查看控制面板區(qū)域語言中的非Unicode語言選項(xiàng)設(shè)置:

系統(tǒng)中設(shè)置的非Unicode語言為英語(美國),這樣系統(tǒng)指向的本地ANSI字符編碼庫就是英語(美國)的ANSI字符編碼庫。

D盤中包含中文字符的文件夾在系統(tǒng)中能正常顯示的,為啥調(diào)用GetModuleFileNameA獲取到的路徑中會(huì)有亂碼呢?系統(tǒng)中顯示的中文字符是Unicode編碼的,當(dāng)我們調(diào)用ANSI版本的GetModuleFileNameA獲取路徑時(shí),GetModuleFileNameA函數(shù)內(nèi)部會(huì)將Unicode編碼的字符串轉(zhuǎn)成ANSI編碼的,轉(zhuǎn)換時(shí)使用的是系統(tǒng)指向的本地ANSI字符編碼庫,也就是英語(美國)的ANSI字符編碼庫,而英語(美國)的ANSI字符編碼庫根本不識(shí)別中文字符,所以出現(xiàn)了亂碼!

GetModuleFileNameA返回的路徑中包含亂碼,導(dǎo)致LoadLibrary失敗,導(dǎo)致GetProcAddress返回NULL值,從而導(dǎo)致call這個(gè)NULL地址產(chǎn)生了異常!

對于當(dāng)前出問題的編解碼庫,需要修改一下代碼,需要調(diào)用Unicode版本的接口。目前臨時(shí)的解決辦法有兩個(gè):

1)將軟件安裝在英文路徑中;

2)在控制面板的區(qū)域語言中將非Unicode語言改成簡體中文。

我們的軟件已經(jīng)聲稱做到了對多語種的支持,雖然UI層已經(jīng)支持Unicode了,但底層的庫因?yàn)槭遣煌_發(fā)團(tuán)隊(duì)開發(fā)維護(hù)的,需要再逐一排查一下了!

以上就是詳解C++中的ANSI與Unicode和UTF8三種字符編碼基本原理與相互轉(zhuǎn)換的詳細(xì)內(nèi)容,更多關(guān)于C++ 字符編碼的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Qt連接數(shù)據(jù)庫并實(shí)現(xiàn)增刪改查操作

    Qt連接數(shù)據(jù)庫并實(shí)現(xiàn)增刪改查操作

    這篇文章主要為大家詳細(xì)介紹了Qt如何連接數(shù)據(jù)庫并實(shí)現(xiàn)增刪改查等基本操作,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09
  • C++內(nèi)存分區(qū)模型超詳細(xì)講解

    C++內(nèi)存分區(qū)模型超詳細(xì)講解

    在了解內(nèi)存分區(qū)之前,我們先來聊一聊為什么要進(jìn)行內(nèi)存分區(qū)。在進(jìn)行了內(nèi)存分區(qū)之后,在不同的區(qū)域存放的數(shù)據(jù),會(huì)有不同的生命周期,從而會(huì)讓程序員的編程變得更加靈活
    2022-11-11
  • C++實(shí)現(xiàn)聊天程序

    C++實(shí)現(xiàn)聊天程序

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)類似QQ聊天程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • c++中c_str()的用法示例

    c++中c_str()的用法示例

    這篇文章主要介紹了c++中c_str()的用法示例,幫助大家更好的理解和學(xué)習(xí)C++,感興趣的朋友可以了解下
    2020-09-09
  • LeetCode題解C++生成每種字符都是奇數(shù)個(gè)的字符串

    LeetCode題解C++生成每種字符都是奇數(shù)個(gè)的字符串

    這篇文章主要為大家介紹了LeetCode題解C++生成每種字符都是奇數(shù)個(gè)的字符串示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • c++ 判斷奇數(shù)偶數(shù)實(shí)例介紹

    c++ 判斷奇數(shù)偶數(shù)實(shí)例介紹

    下面通過判斷一個(gè)數(shù)是偶數(shù)還是奇數(shù)來展示交互遞歸的應(yīng)用,并且此題突出了遞歸跳躍的信任的重要性,需要的朋友可以參考下
    2012-11-11
  • C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析

    C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析

    這篇文章主要介紹了C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • C語言歸排與計(jì)排深度理解

    C語言歸排與計(jì)排深度理解

    這篇文章主要為大家詳細(xì)的介紹了C語言中計(jì)數(shù)排序和歸并排序,歸并排序是創(chuàng)建在歸并操作上的一種有效的排序算法,計(jì)數(shù)排序不用比較兩個(gè)數(shù)的大小,感興趣的朋友可以參考閱讀
    2023-04-04
  • 基于Matlab制作一個(gè)不良圖片檢測系統(tǒng)

    基于Matlab制作一個(gè)不良圖片檢測系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了如何基于Matlab制作一個(gè)不良圖片檢測系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起了解一下
    2022-07-07
  • 詳解C語言中函數(shù)宏的三種封裝方式

    詳解C語言中函數(shù)宏的三種封裝方式

    函數(shù)宏,即包含多條語句的宏定義,其通常為某一被頻繁調(diào)用的功能的語句封裝,且不想通過函數(shù)方式封裝來降低額外的彈棧壓棧開銷。本文就來聊聊函數(shù)宏的三種封裝方式吧
    2023-03-03

最新評論