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了,但底層的庫因為是不同開發團隊開發維護的,需要再逐一排查一下了!
以上就是詳解C++中的ANSI與Unicode和UTF8三種字符編碼基本原理與相互轉換的詳細內容,更多關于C++ 字符編碼的資料請關注服務器之家其它相關文章!
原文鏈接:https://blog.csdn.net/chenlycly/article/details/121070073