主頁 >  其他 > 一文帶你弄懂C++中的ANSI、Unicode和UTF8三種字符編碼

一文帶你弄懂C++中的ANSI、Unicode和UTF8三種字符編碼

2021-11-02 10:13:12 其他

目錄

1、概述

2、Visual Studio中的字符編碼

3、ANSI窄位元組編碼

4、Unicode寬位元組編碼

5、UTF8編碼

6、如何使用字符編碼

7、三種字符編碼之間的相互轉換(附原始碼)

7.1、ANSI編碼與Unicode編碼之間的轉換

7.2、UTF8編碼與Unicode編碼之間的轉換

7.3、ANSI編碼與UTF8編碼之間的轉換

8、Windows系統對使用ANSI窄位元組字符編碼的程式的兼容

9、字符編碼導致程式啟動失敗的案例


在C++編程中,我們有時需要去處理字串編碼的相關問題,常見的字符編碼有ANSI窄位元組編碼Unicode寬位元組編碼UTF8可變長編碼,很多人在處理字串編碼問題時都會有疑惑,即便是有多年作業經驗的朋友也可能搞不清楚,所以有必要講一下這三種字符編碼以及如何去使用它們,

1、概述

在日常的軟體開發程序中,會時不時地去處理不同編碼格式的字串,特別是在處理檔案路徑的相關場景中,比如我們要通過路徑去讀寫檔案、通過路徑去加載庫檔案等,常見的字符編碼格式有ANSI窄位元組編碼、Unicode寬位元組編碼以及UTF8可變長編碼,在Linux系統中,主要使用UTF8編碼;在Windows系統中,既支持ANSI編碼,也支持Unicode編碼,

通用的大小寫字母和數字則使用全球統一的固定編碼,即ASCII碼,

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

Unicode編碼則是全球統一的雙位元組編碼,所有語種的字符在一起統一的編碼,每個字符的編碼都是全球唯一的,

UTF8編碼是一種可變長的寬位元組編碼,也是一種全球統一的字符編碼,

本文將以WIndows中使用Visual Studio進行C++編程時需要處理的字符編碼問題為切入點,詳細講解一下字符編碼的相關內容,

2、Visual Studio中的字符編碼

在Visual Studio中撰寫C++代碼時,該如何指定字串的編碼呢?其實很簡單,使用雙引號括住的字串,使用的就是ANSI窄位元組編碼;使用L+雙引號括住的字串,使用的就是Unicode寬位元組編碼,如下所示:

char* pStr = "This is a Test.";    // ANSI編碼
WCHAR* pWStr = L"This is a Test."; // Unicode寬位元組編碼

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

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

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

如果選擇多位元組字符集,_T就被解釋為雙引號,即使用ANSI窄位元組編碼;如果選擇Unicode字符集,_T就被解釋為L,即使用Unicode寬位元組編碼,

其實,如果在工程配置中選擇使用Unicode字符集,工程中會添加一個_UNICODE宏,如下所示:

如果選擇多位元組字符集,則沒有_UNICODE宏,代碼中正是通過這個宏來判定到底使用哪種編碼的,比如對_T的判斷:

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

和字符編碼相對應的,Windows系統提供兩個版本的API,比如給視窗設定文字的API函式,一個是支持ANSI窄位元組編碼的SetWindowTextA(ANSI窄位元組版本),一個是支持Unicode寬位元組編碼的SetWindowTextW(Wide寬位元組版本),我們也可以直接呼叫SetWindowText,然后由_UNICODE宏判斷到底使用哪個版本,如下:

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

3、ANSI窄位元組編碼

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

ANSI編碼是個本地范疇,只適用于對應的語種,每個字符的編碼不是全球唯一的編碼,只在對應的語種中有效,對于中文GB2312編碼的字串,如果當成英文的ANSI編碼來決議,則結果會是亂碼!

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

使用中文ANSI編碼的字串開發的程式(代碼中使用的都是中文字串,使用的是ANSI窄位元組編碼),拿到俄文作業系統中可能顯示的都是亂碼,因為在俄文的ANSI編碼中只識別俄文的ANSI編碼出來的字串,無法識別中文ANSI編碼的字串,這里主要有兩類字符亂碼問題,一是UI界面上顯示的文字是亂碼;二是使用路徑去創建檔案或訪問檔案時會因為路徑中的字符是亂碼,導致檔案創建或訪問失敗,

4、Unicode寬位元組編碼

Unicode編碼是全球統一的字符編碼,每個語種下的每個字符的編碼值都是全球唯一的,即在Unicode編碼集中可以識別每個語種下的所有字符,所以為了實作軟體對多語種(多國語言)的支持,我們在開發軟體時要選擇Unicode字符編碼,使用Unicode編碼的字串,呼叫Unicode版本的API

系統在提供包含字串引數的API時,都會提供兩個版本,一個是ANSI版本的,一個是Unicode版本的,主要體現在對字串編碼的處理上,比如SetWindowTextA(ANSI版本)和SetWindowTextW(Wide寬位元組Unicode版本),我們可以直接呼叫W版本API,但一般我們呼叫API時,我們不指定呼叫哪個版本,是通過設定工程屬性中的編碼格式來確定使用哪個版本:

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

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

在Unicode編碼中,每個字符都占兩個位元組,對于大小寫字母和數字,當他們出現在字串中時,對應的記憶體中存放的是它們的ASCII碼值,只占一個位元組,在Unicode 2位元組編碼中,高位將填充0,

5、UTF8編碼

UTF8編碼是可變長字符編碼格式,是一種緊湊型存盤的編碼格式,也是一種寬位元組的、全球統一的編碼格式,UTF8編碼中的所有字符,包括不同語種下面的字符,都是全球唯一編碼的,在所有的系統都能識別出來,

UTF8編碼中,能用一個位元組存放的,就用一個位元組存放,比如大小寫字母和數字,在字串中存放的ASCII碼,只需要一個位元組去存放就夠了,所以在UTF8編碼中,大小寫字母和數字只占一個位元組,我們常用的中文字符,一個字符則占用3個位元組,

UTF8編碼之所以稱之為可變長的,是因為其根據字符需要的實際存盤空間大小來編碼的,比如大小寫字母和數字的存盤只需要1個位元組就夠了,所以它們只占一個位元組,而一個中文字符則占三個位元組,

6、如何使用字符編碼

Windows系統主要使用Unicode編碼,Linux則使用UTF8編碼,后臺服務器一般使用的都是Linux系統,而客戶端是運行在Windows作業系統上的,一般客戶端與服務器互動的資料的字串編碼統一使用全球統一編碼的UTF8編碼,

客戶端收到UTF8編碼的字串后,需要將UTF8字符換轉換后顯示在界面上,如果客戶端使用的是Unicode編碼字符集,將UTF8編碼的字串轉換成Unicode編碼的字串后再顯示到界面上;如果客戶端使用的是多位元組ANSI編碼,則需要再將Unicode編碼的字串轉成ANSI編碼的字串,

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

為了實作軟體對多語種(多國語言)的支持,我們在開發Windows軟體時要選擇Unicode字符編碼,使用Unicode編碼的字串,呼叫Unicode版本的API,

此外,對于一些開源的專案,提供的API介面中有字串引數的,一般都明確指定字串編碼為UTF8,因為一般情況下開源庫都支持跨平臺,既支持Windows平臺,也支持Linux平臺,所以要選擇使用通用的、大家都是識別的UTF8編碼,

比如在輕便型資料庫sqlite開源庫中,用于打開資料庫檔案的介面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編碼的字串,在呼叫sqlite3_open介面之前,需要將Unicode編碼的字串轉成UTF8編碼的,如果收到開源庫中回呼上來的UTF8編碼的字串資料,則需要將UTF8編碼的字串轉成Unicode后,才能顯示到UI界面上,才能使用轉碼后的Unicode字串去呼叫Windows系統API,

7、三種字符編碼之間的相互轉換(附原始碼)

有朋友曾經提出這樣的疑問,是不是我在Windows下把一個雙引號括起來的ANSI窄位元組字串賦值給WCHAR寬位元組的指標

WCHAR* pStr = "測驗字串";

字串就能自動轉換成Unicode寬位元組?答案是否定的,這樣的賦值操作并不會做字符編碼轉換,右側的僅僅是字串的首地址,作為地址,可以賦值給很多資料型別,比如int、void*、char*等等,

那可能有人會說,那為啥我在Unicode下,將一個ANSI編碼的字串傳給MFC庫中的CString類物件時會自動轉換成Unicode寬字符呢?這和上面的情況不一樣的,是因為CString類多載了賦值運算子函式,在函式內部做了字符編碼的轉換,代碼如下:

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

一般情況下,是需要我們自己去撰寫字符編碼轉換的代碼的,下面來看一下,我們在進行Windows C++編程時,需要呼叫哪些API介面實作上述三種編碼之間的轉換,

7.1、ANSI編碼與Unicode編碼之間的轉換

ANSI轉成Unicode的代碼如下:

/*=============================================================================
函 數 名: AnsiToUnicode
功    能: 實作將char型buffer(ANSI編碼)中的內容安全地拷貝到指定的WChar型(Unicode編碼)的buffer中
參    數: char*  pchSrc [in]          源字串
          WCAHR* pchDest [out]        目標buf    
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
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轉成ANSI的代碼如下:

/*=============================================================================
函 數 名: UnicodeToAnsi
功    能: 實作將WCHAR型buffer(Unicode編碼)中的內容安全地拷貝到指定的char型(ANSI編碼)的buffer中
參    數: WCHAR*  pchSrc [in]         源字串
		  char*   pchDest[out]        目標buf
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
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編碼之間的轉換

UTF8轉成Unicode的代碼如下:

/*=============================================================================
函 數 名: Utf8ToUnicode
功    能: 實作將char型的buffer(utf8編碼)中的內容安全地拷貝到指定的WCHAR型buffer(Unicode編碼)中
參    數: char* pchSrc [in]          源字串
          WCHAR* pchDest [out]     目標buf		 
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
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轉成UTF8的代碼如下:

/*=============================================================================
函 數 名: UnicodeToUtf8
功    能: 實作將WCHAR型buffer(Unicode編碼)中的內容安全地拷貝到指定的char型的buffer(utf8編碼)中
參    數: WCAHR* pchSrc [in]          源字串
          char* pchDest [out]     目標buf	  
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
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編碼之間的轉換

ANSI與UTF8之間是不能直接轉換的,需要先轉成Unicode之后才能轉到目標編碼,

ANSI轉成UTF8的代碼如下:

/*=============================================================================
函 數 名: AnsiToUtf8
功    能: 實作將char型buffer(ANSI編碼)中的內容安全地拷貝到指定的char型的buffer(utf8編碼)中
參    數: char* pchSrc [in]          源字串
          char* pchDest [out]     目標buf
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void AnsiToUtf8( const char* pchSrc, char* pchDest, int nDestLen ) 
{
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}

	// 先將ANSI轉成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轉成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轉成ANSI的代碼如下:

/*=============================================================================
函 數 名: Utf8ToAnsi
功    能: 實作將char型buffer(utf8編碼)中的內容安全地拷貝到指定的char型的buffer(ANSI編碼)中
參    數: char* pchSrc [in]          源字串
		  char* pchDest [out]     目標buf
		  int nDestLen [in]           目標buf長度(注意:以位元組為單位,不是以字符個數為單位)
注    意: 無
返 回 值: 無
=============================================================================*/
void Utf8ToAnsi(const char* pchSrc, char* pchDest, int nDestLen)
{
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}

	// 先將utf8轉成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轉成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系統對使用ANSI窄位元組字符編碼的程式的兼容

現在的Windows程式基本都用Unicode字符編碼了,工程屬性中將字符集都設定成了Unicode字符集,代碼中都使用Unicode編碼的字串,但是還有一些老的程式使用的還是ANSI窄位元組的字符,那這些老的程式如何才能在外文的作業系統中正常運行呢?微軟提供了一種兼容這些老程式的辦法,

可以到Windows控制面板的區域語言設定中將非Unicode語言設定成程式中使用的字符語種即可,相關設定的操作步驟截圖如下:

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

下面我們來看看使用ANSI編碼的程式放到外文作業系統中運行為什么會出現亂碼,假設將某程式中使用的是中文ANSI窄位元組編碼的字串,放到英文作業系統中運行,默認情況下,UI界面上會顯示亂碼,至于為什么會顯示亂碼,是因為英文作業系統中默認情況下設定的非Unicode語言是英語(美國):

這個非Unicode語言設定直接影響我們呼叫MultiByteToWideChar和WideCharToMultiByte介面中的CP_ACP標記對應的本地ANSI字符集編碼庫,在上面界面中如果將非Unicode語言設定成英語(美國),則使用英文的ANSI字符編碼庫;如果設定成中文簡體,則使用中文簡體的ANSI字符集編碼庫,

程式中呼叫API函式SetWindowTextA給程式中的視窗設定文字或標題時,傳入的字串是ANSI窄位元組編碼的,而SetWindowTextA函式內部及底層的流程中會使用本地設定的ANSI字符集編碼庫將ANSI編碼的字串轉成Unicode編碼的字串后再設定到視窗中,最終界面上看到的文字是Unicode編碼的文字,所以在將中文字符轉換成Unicode時,如果使用的是本地設定的英文字符集編碼進行轉換,則會出現亂碼;如果使用中文簡體的字符集編碼進行轉換,則能正常顯示,

所以,要讓使用中文ANSI編碼字符的程式能在英文作業系統中正常顯示并運行,需要將英文作業系統中區域語言設定項中的“非Unicode程式的語言”設定成中文才行,

9、字符編碼導致程式啟動失敗的案例

幾天前正好排查了一例因為字符編碼導致的程式啟動失敗的實體,在這里簡單的說一下,客戶將軟體安裝到一個包含中文字符的路徑中,點擊啟動軟體沒反應,軟體始終啟動不了,也沒有彈出什么報錯的提示框,客戶于是向我們反饋了這個問題,

我們使用向日葵遠程到客戶的機器上,經對比發現,如果我們將軟體安裝到默認的C:\Program Files(X86)的英文路徑下,程式是能正常啟動的,所以我們初步懷疑可能是字符編碼引起的問題,重新將軟體安裝到D盤包含中文字符的路徑后,我們用windbg啟動軟體,剛啟動windbg中就檢測到看例外,例外發生在加載主程式依賴庫的程序中,

啟動軟體的exe主程式時,會將該exe依賴的所有庫依次加載到行程空間中,待所有的庫都加載起來后,才會將exe主程式模塊啟動起來,才能看到軟體的主界面,

如果在加載庫時產生了例外,整個啟動程序將被終止,軟體也就無法啟動了,

例外發生在加載音視頻編解碼庫mediaproc.dll中,于是在windbg中輸入kn命令,查看例外時的函式呼叫堆疊(事先已經取來了pdb符號檔案),呼叫堆疊顯示時崩潰在mediaproc.dll庫的DllMain函式中,加載dll庫時都會呼叫到該介面,

根據呼叫堆疊中顯示的代碼行號,到編解碼庫的源代碼中查看,發現是崩潰在一個函式介面指標的呼叫上,有可能是遇到空指標了,一般情況下,使用windbg實時除錯時是能看到函式中的區域變數及類物件記憶體中的值,但這次有點特殊,看不到記憶體中的值,

于是和負責維護音視頻編解碼庫的同事溝通了一下, 編解碼庫mediaproc.dll在DllMain中會使用絕對路徑(當前exe主程式的路徑)去呼叫LoadLibrary去動態加載更底層的庫,然后呼叫GetProcAddress把底層庫的介面都拿出來保存到指標變數中,編解碼庫mediaproc.dll是呼叫ANSI版本的API函式GetModuleFileNameA獲取exe主程式的路徑,問題就出在這個函式的呼叫上,這個函式獲取的路徑中包含亂碼,

D盤包含中文字符的檔案夾在系統中是能正常顯示的,為啥獲取的路徑中會包含亂碼呢?于是查看了客戶Windows作業系統版本,是Windows10 IOT版本,經常見到旗艦版、專業版和教育版,這個IOT版本還是第一次遇到!于是又去查看控制面板區域語言中的非Unicode語言選項設定:

系統中設定的非Unicode語言為英語(美國),這樣系統指向的本地ANSI字符編碼庫就是英語(美國)的ANSI字符編碼庫,

D盤中包含中文字符的檔案夾在系統中能正常顯示的,為啥呼叫GetModuleFileNameA獲取到的路徑中會有亂碼呢?系統中顯示的中文字符是Unicode編碼的,當我們呼叫ANSI版本的GetModuleFileNameA獲取路徑時,GetModuleFileNameA函式內部會將Unicode編碼的字串轉成ANSI編碼的,轉換時使用的是系統指向的本地ANSI字符編碼庫,也就是英語(美國)的ANSI字符編碼庫,而英語(美國)的ANSI字符編碼庫根本不識別中文字符,所以出現了亂碼!

GetModuleFileNameA回傳的路徑中包含亂碼,導致LoadLibrary失敗,導致GetProcAddress回傳NULL值,從而導致call這個NULL地址產生了例外!

對于當前出問題的編解碼庫,需要修改一下代碼,需要呼叫Unicode版本的介面,目前臨時的解決辦法有兩個:

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

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

我們的軟體已經聲稱做到了對多語種的支持,雖然UI層已經支持Unicode了,但底層的庫因為是不同開發團隊開發維護的,需要再逐一排查一下了!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/344247.html

標籤:其他

上一篇:【資料結構】——二叉搜索樹

下一篇:《萬人千題》社區打卡,引燃你對青(刷)春(題)的渴望

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more