目錄
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
標籤:其他
上一篇:【資料結構】——二叉搜索樹
