VC++源文件編碼


1源代碼文件    1

1.1 研究思路    1

1.2 實驗結果    3

1.3 #pragma setlocale    4

1.4 /source-charset    5

1.5 使用UTF-8字符串    6

1.5.1 /execution-charset    6

1.5.2 #pragma execution_character_set    7

1.5.3 u8    7

1.5.4 /utf-8/validate-charset    8

1.6 總結    8

2資源文件    9

2.1 ANSI編碼    9

2.2 UTF16LE編碼    10

2.3 總結    11

 

 

1源代碼文件

VC++源代碼文件(*.h;*.hpp;*.c;*.cxx;*.cpp;*.inl……),在VC++6.0時都是ANSI編碼,從VC++2002開始逐步支持Unicode編碼。在不同的文件編碼下,VC++編譯器對字符串的編譯處理是不同的。本文對此進行詳細說明。

1.1 研究思路

如下表所示,生成四個源代碼文件:

//ANSI936.cpp 保存為 ANSI 編碼(代碼頁為 936 GBK

static char* s = "936-ssssss-";

static wchar_t* w = L"936-wwwwww-";

//UTF8.cpp 保存為 UTF-8 編碼(有BOM

static char* s = "utf8-ssssss-®";

static wchar_t* w = L"utf8-wwwwww-®";

//UTF16BE.cpp 保存為 UTF-16編碼(高位字節在前,有BOM

static char* s = "utf16BE-ssssss-®";

static wchar_t* w = L"utf16BE-wwwwww-®";

//UTF16LE.cpp 保存為 UTF-16編碼(低位字節在前,有BOM

static char* s = "utf16LE-ssssss-®";

static wchar_t* w = L"utf16LE-wwwwww-®";

使用VC++打開編譯后生成的exe文件。如下圖所示,請單擊"Open"按鈕的右端,彈出菜單中,請單擊【Open With...】菜單項

1.1

顯示如下對話框:

圖1.2

上圖中,選擇"Binary Editor"(二進制編輯器),然后單擊OK按鈕。就以二進制方式打開exe文件了。如下圖所示:

圖1.3

在這個exe文件里查找"-ssssss-"即可找到窄字符串(char*)的編碼,如下圖所示:

圖1.4

上圖中"-ssssss-"前面有utf16BE,說明該字符串在文件UTF16BE.cpp里,編碼為UTF16BE。重點是"-ssssss-"后面的A3 C1 3F 00。"A3 C1"是"A"(全角字符A)的GBK編碼,3F是"®"的GBK編碼(GBK里沒有字符®,所以用?表示)。

同理,在exe文件里查找"-wwwwww-"即可找到寬字符串(wchar_t*)的編碼。

1.2 實驗結果

實驗結果如下所示

編譯器

char*s = "...";

wchar_t* w = L"...";

2002

ANSI936.cpp

ANSI

UTF16LE

UTF8.cpp

UTF16BE.cpp

UTF16LE.cpp

無法編譯

無法編譯

2003

ANSI936.cpp

ANSI 

UTF16LE 

UTF8.cpp

UTF16BE.cpp

UTF16LE.cpp

UTF-8

UTF16LE

2005

2015 

ANSI936.cpp

ANSI 

UTF16LE 

UTF8.cpp

UTF16BE.cpp

UTF16LE.cpp

ANSI 

UTF16LE 

說明:

1、從vc2005~vc2015,均能正常編譯生成窄字符串(char*,編碼是ANSI編碼)和寬字符串(wchar_t*,編碼是UTF-16LE);

2Unicode編碼的文件(UTF8.cppUTF16BE.cppUTF16LE.cpp)中含有字符"®"。編譯窄字符串時,會將該字符由Unicode編碼轉換為ANSI編碼(代碼頁是936 GBK)。代碼頁GBK里沒有字符"®",編譯時會顯示警告:

w:\vc\test\src\utf16le.cpp(5): warning C4566: character represented by universal-character-name '\u00AE' cannot be represented in the current code page (936)

3vc2002Unicode源文件的支持度不高——含有字符A®的字符串無法編譯;

4vc2003編譯生成的窄字符串編碼比較有意思:其編碼不是ANSI編碼,而是UTF-8編碼。因此,筆者猜測——編譯器遇到Unicode編碼的源文件時,會將其轉換為UTF-8編碼進行編譯。

1.3 #pragma setlocale

編譯生成寬字符串時,有時需要指定ANSI代碼頁。如下面的代碼:

//UTF8.cpp 保存為 UTF-8 編碼(有BOM

static wchar_t* w = L"utf8-wwwwww-®";

//UTF16BE.cpp 保存為 UTF-16編碼(高位字節在前,有BOM

static wchar_t* w = L"utf16BE-wwwwww-®";

//UTF16LE.cpp 保存為 UTF-16編碼(低位字節在前,有BOM

static wchar_t* w = L"utf16LE-wwwwww-®";

//ANSI936.cpp 保存為 ANSI 編碼(代碼頁為 936 GBK

static wchar_t* w1 = L"936-wwwwww-";

#pragma setlocale(".950")

static wchar_t* w2 = L"950-wwwwww-";

前三個文件,因為都是Unicode編碼,轉換為寬字符串時無需指定ANSI代碼頁。

文件ANSI936.cpp比較有意思:

1"936-wwwwww-"的編碼是936 GBK,編譯時需要將其轉換為Unicode編碼,使用的代碼頁就是系統默認的ANSI代碼頁,簡體中文下就是936 GBK。因此,在簡體中文操作系統下,L"936-wwwwww-"能夠被正確的編譯;

2"950-wwwwww-"的編碼是Big5。字符"⑾"的編碼是A2 CF,這個編碼在代碼頁950 Big5下就是字符"A";在代碼頁936 GBK下就是字符"⑾"。編譯器在編譯L"950-wwwwww-"時,因為#pragma setlocale(".950")指定ANSI代碼頁為950 Big5,所以"950-wwwwww-"中的"⑾"將被轉換為"A"。

VC++6.0時代,就是靠#pragma setlocale在代碼里實現多語言的。如下面的代碼:

//ANSI936.cpp 保存為 ANSI 編碼(代碼頁為 936 GBK

#pragma setlocale(".936")

static wchar_t* w1 = L"936-wwwwww-";

#pragma setlocale(".950")

static wchar_t* w2 = L"950-wwwwww-";

不論操作系統的語言是什么,只要安裝了代碼頁936950,就能夠正常編譯上述代碼。

注意:

1#pragma setlocale只在ANSI編碼的文件里有效,且只對寬字符串有效;

2#pragma setlocale設置代碼頁時,代碼頁的編碼最多只能有兩個字節。如:#pragma setlocale(".65001")希望設置代碼頁為UTF-8,是不會成功的。因為UTF-8編碼有可能超過兩個字節。

1.4 /source-charset

簡體中文操作系統下,源文件保存為ANSI編碼時,代碼頁為936 GBK

繁體中文操作系統下,源文件保存為ANSI編碼時,代碼頁為950 Big5

………………

假如某個源文件的編碼是ANSI編碼,且是在繁體中文操作系統下編輯而成的。拿到簡體中文操作系統下編譯,編譯器就會錯誤的把代碼頁950 Big5里的字符當做代碼頁936 GBK里的字符進行編譯,有可能會產生亂碼。

此時,可以將這個文件的編碼轉換為UTF-8編碼。

如果不想修改文件,可以設置編譯器,如下圖所示:

圖1.5

上圖中,選項"/source-charset:.950"設置文件"ANSI950.cpp"的代碼頁為950

注意/source-charset#pragma setlocale的區別:#pragma setlocale(".950")僅僅影響寬字符串;/source-charset:.950會把整個文件的編碼當做ANSI 950,也就是說它不僅影響寬字符串,還影響窄字符串。

1.5 使用UTF-8字符串

Linux的窄字符串(char*)默認是UTF-8編碼,因此很多開源的C/C++庫使用的窄字符串也是UTF-8編碼,如:SQLiteTinyXML……

VC++的窄字符串默認為ANSI編碼,為了使用上述開源庫,需要在ANSI編碼與UTF-8編碼之間來回轉換。

VC++在編譯時,能不能直接編譯成UTF-8字符串?從vc2010后,這方面的支持在逐步增強。

1.5.1 /execution-charset

如下圖所示,給整個項目添加選項"/execution-charset:utf-8"。其含義是:編譯窄字符串時,將其編碼轉換為UTF-8編碼。

圖1.6

按上圖設置后,下面的窄字符串將統統變為UTF-8編碼。

//ANSI936.cpp 保存為 ANSI 編碼(代碼頁為 936 GBK

static char* s = "936-ssssss-";

//UTF8.cpp 保存為 UTF-8 編碼(有BOM

static char* s = "utf8-ssssss-®";

//UTF16BE.cpp 保存為 UTF-16編碼(高位字節在前,有BOM

static char* s = "utf16BE-ssssss-®";

//UTF16LE.cpp 保存為 UTF-16編碼(低位字節在前,有BOM

static char* s = "utf16LE-ssssss-®";

千萬不要把這些字符串傳給ANSI版本的Windows API,如:MessageBoxA,因為這些API需要的是ANSI編碼的字符串,而不是UTF-8編碼的字符串。

1.5.2 #pragma execution_character_set

/execution-charset的殺傷半徑太大——要么影響某個文件,要么影響整個項目。使用#pragma execution_character_set會更好些。如下面的代碼:

#pragma execution_character_set("utf-8")

const char* s1 = "11111-";

#pragma execution_character_set(".ACP")

const char* s2 = "22222-";

字符串"11111-":因為#pragma execution_character_set("utf-8")的緣故,將被編譯為UTF-8編碼;

字符串"22222-":因為#pragma execution_character_set(".ACP")的緣故,將被編譯為ANSI編碼。代碼頁是系統默認的代碼頁,在簡體中文操作系統下,就是936 GBK

1.5.3 u8

vc2015開始,編譯器支持u8字符串,如下面的代碼:

static char* s = u8"utf8-ssssss-®";

字符串增加了前綴u8,表示編譯后該字符串的編碼為UTF-8

有了u8字符串,就不要用/execution-charset#pragma execution_character_set了。

1.5.4 /utf-8/validate-charset

/utf-8相當於同時設置"/source-charset:utf-8"和"/execution-charset:utf-8"。

/validate-charset 的含義是:檢查字符串里的字符是否合法。如下面的代碼:將Unicode編碼的字符串轉換為GBK時,因為字符®GBK里不存在,因此編譯時應該提示用戶。選項"/validate-charset"表示檢查並生成警告信息;選項"/validate-charset-"表示不檢查。(筆者實際測試發現:不管怎么設置,都會產生警告信息)

//UTF8.cpp 保存為 UTF-8 編碼(有BOM

static char* s = "utf8-ssssss-®";

1.6 總結

1vc6~vc2003的源代碼文件,請使用ANSI編碼,不要使用Unicode編碼;多語言支持的要點有兩個:一是使用寬字符串;二是使用#pragma setlocale設置代碼頁;

2vc2005~vc2015的源代碼文件,建議使用有BOMUTF-8編碼。字符串盡量使用寬字符串。這樣,就不會因為編碼轉換導致亂碼。®Unicode編碼轉換為GBK碼生成亂碼?就是一個例子;

3、將源代碼文件由ANSI編碼升級為UTF-8編碼時,一定要謹慎處理#pragma setlocale語句;

4VC++編譯時就生成UTF-8字符串的方法:1vc6~vc2008編譯器不支持,請自行編碼實現;2vc2010~vc2013可使用#pragma execution_character_set3vc2015可使用u8關鍵詞。

 

 

2資源文件

2.1 ANSI編碼

經筆者測試發現:vc2002vc2003雖然編譯.rc文件時支持UTF16LEUTF16BE編碼,但是不能編輯。因此,vc6~vc2003.rc文件,請使用ANSI編碼。

下圖表示資源文件里,同一個字符串IDS_STRING129在兩個字符串表里。一個是936代碼頁的,另一個是950代碼頁的。

圖2.1

記事本查看.rc文件,其內容精簡如下:

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#pragma code_page(936)

STRINGTABLE

BEGIN

IDS_STRING129 "936"

END

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL

#pragma code_page(950)

STRINGTABLE

BEGIN

IDS_STRING129 "950"

END 

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED表示其接下來的資源屬於簡體中文;#pragma code_page(936)表示接下來的字符串,其代碼頁為936。"936A"的代碼頁是936,在簡體中文操作系統下,它能夠正常顯示。

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL表示其接下來的資源屬於繁體中文;#pragma code_page(950)表示接下來的字符串,其代碼頁為950。"950A"的代碼頁是950,在簡體中文操作系統下,它不能正常顯示。

2.2 UTF16LE編碼

經筆者測試發現:vc2005~vc2015rc文件支持ANSIUTF16BEUTF16LE編碼,不支持UTF-8編碼。

下圖表示資源文件里,同一個字符串IDS_STRING129在兩個字符串表里。一個是936代碼頁的,另一個是950代碼頁的。

圖2.2

記事本查看.rc文件,其內容精簡如下:

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

STRINGTABLE

BEGIN

IDS_STRING129 "936"

END

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL

STRINGTABLE

BEGIN

IDS_STRING129 "950"

END 

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED表示其接下來的資源屬於簡體中文;LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL表示其接下來的資源屬於繁體中文。

兩個字符串"936A""950A"均屬於Unicode編碼,因此兩個"A"均能在記事本里正常顯示。

2.3 總結

1vc6~vc2003.rc文件,請使用ANSI編碼。簡體中文的資源請在簡體中文操作系統下編輯;繁體中文的資源請在繁體中文操作系統下編輯……盡量不要使用記事本來編輯.rc文件;

2vc2005~vc2015.rc文件,請使用UTF16LE編碼;

3、將.rc文件由ANSI編碼升級為UTF16LE編碼時,可刪除語句#pragma code_page。但是一定要注意字符串的ANSI編碼,將其轉換為正確的Unicode編碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM