《windows核心編程系列》二談談ANSI和Unicode字符集 .
http://blog.csdn.net/ithzhang/article/details/7916732轉載請注明出處!!
第二章:字符和字符串處理
使用vc編程時項目--》屬性--》常規欄下我們可以設置項目字符集合,它可以是ANSI(多字節)字符集,也可以是unicode字符集。一般情況下說Unicode都是指UTF-16。也就是說每個字符編碼為兩個字節。65535個字符可以表示世界上大部分的語言。為了軟件使國際化大家再編程時應該使用unicode字符集。由于原來學過c語言,不習慣使用Unicode,為了省事而直接在配置屬性里調為多字節字符集,這是個不好的習慣。C語言的字符串,以及對這些字符串操作的函數都是不安全的。很容易導致緩沖區溢出錯誤,有時這會導致很嚴重的后果。所以極力建議大家要使用Unicode字符串,對它們進行的操作要使用接下來將要介紹的安全字符串函數。除此之外這對于提高性能也有幫助,因為現在大部分的windowsAPI都是直接對Unicode字符串進行操作,你所傳入的多字節字符串會先轉換為Unicode后再拿來使用。直接使用Unicode省略了轉換的步驟,當然具有更高的性能。
C語言定義字符時使用的char類型,表示一個8位ANSI字符。
在vc中,microsoft定義了一個內置的數據類型wchar_t,它表示一個16位的Unicode字符。它是通過下面的語句定義的:
typedef unsigned short wchar_t;
很明顯它占16位。定義Unicode字符與ANSI類似。如:
wchar_t a=L'a';
wchar_t array[100]=L"Hello world!!!";
對于array數組,它的每個字符都占16位且是以16位的\0結尾。
字符和字符串前的L是一個宏。它通知編譯器應該將其編譯為Unicode字符串。當編譯器將此字符串放入程序的數據段時,會使用UTF-16來編碼每個字符。
剛開始接觸windows開發的同學會對windows提供的各種內置類型不知所措。其實這是windows為了顯示出自己的與眾不同將原來大家熟悉的c語言的內置類型起個別名罷了。如
typedef char CHAR;
typedef wchar_t WCHAR;
typedef CHAR *PCHAR;
typedef CHAR *PSTR;
typedef COSNT CHAR *PCSTR;
typedef WCHAR *PWCHAR;
typedef WCHAR *PWSTR;
typedef COSNT WCHAR *PCWSTR;
其實使用哪種數據類型并不重要,關鍵是能保持一致性。不要混合使用。如果是windows程序員最好能與windows保持一致。這可以增強代碼的可讀性。
為什么編譯器能根據我們的設置自動為我們選擇字符集呢。原來在本章開始時當我們選擇使用Unicode字符集時,編譯器會為我們在源文件中定義Unicode。形如#define Unicode。如果選擇使用多字節字符集時會將定義Unicode的語句注釋掉。為什么僅僅使用一句話就可以有這么大改變呢。請接著往下看:
#define UNICODE
typedef WCHAR TCHAR,*PTCHAR , PTSTR;
typedef COSNT WCHAR *PCTSTR;
#define _TEXT(quote) L##quote
#else
typedef CHAR TCHAR ,*PTCHAR,PTSTR;
typedef CONST CHAR *PCTSTR;
#define _TEXT(quote) quote
#endif
#define TEXT(quote) _TEXT(quote)
原來編譯器使用預編譯宏來進行判斷,如果定義了UNICODE,就會將WCHAR定義為TCHAR,在_TEXT,在TEXT的參數前加上L。否則將TCHAR定義為CHAR,TEXT,_TEXT的參數前沒有添加任何東西。明白了這些我們就應該清楚如何寫出兼容性強的代碼,也就是說讓我們的代碼在Unicode或是多字節字符集下都可以成功運行。或許你應經看出來了,使用TCHAR和TEXT。如:
TCHAR c=TEXT('a');
TCHAR array[100]=TEXT("Hello world!");
編譯器會根據我們的設置,編譯成不同的代碼。在Unicode環境下,由于定義了UNICODE上述宏經過展開后會生成以下代碼:
WCHAR c=L'a';
WCHAR array=L"Hello world!";
而在ANSI字符集下會生成這樣的代碼:
CHAR c='a';
CHAR array="Hello world!";
能看懂上述宏定義很重要,因為在vc中我們使用的DEBUG和RELEASE模式就是使用上述方法輕松轉換的。當我們選擇DEBUG模式時編譯器會在源代碼中定義_DEBUG。RELEASE模式時會定義NDEBUG。預編譯指令會對上述定義進行判斷,根據定義的不同生成不同的代碼。對上述宏不懂得同學可以找下介紹宏的資料來看,宏參數、宏展開什么的。
正如前面介紹的,現在的windows版本內部處理字符串都是使用Unicode字符集,調用windows函數時如果傳入的是一個ANSI字符串,函數首先會執行轉換操作,將ANSI字符串轉換為Unicode字符串。
如果一個windows函數參數列表中存在字符串,它通常有兩個版本。一個接受Unicode字符串另一個接受ANSI字符串。比如大家比較熟悉的MessageBox,它就有兩個版本,一個是MessageBoxA,處理ANSI字符串另一個是MessageBoxW用以處理Unicode字符串。既然MessageBox有兩個版本為什么平時我們使用時只需要使用MessageBox就行了。這個還是跟宏定義有關。在windows提供的頭文件中有這樣一句話:
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
有了上面的介紹是不是這句話很容易的就看出來了。對了,MessageBox并不是真正的函數,它僅僅是一個宏罷了。根據是否定義UNICODE決定在源文件中使用哪個函數。很多函數都存在這樣的情況。這也是很多新手在逆向工程函數攔截時使用如MessageBox卻一無所獲的原因。
當我們使用ANSI字符集時,UNICODE不會被定義,真正使用的是MessageBoxA函數,該函數首先執行將ANSI字符串轉換為Unicode,然后再調用MessageBoxW函數。W版本返回的字符串也要先轉換成ANSI字符串才被能被使用。與直接使用W版本相比這當然性能更低。
Windows的這種做法很好的兼容了Unicode和ANSI。可以考慮在我們自己的程序中使用這種方法。比如導出dll時,可以考慮導出兩個函數一個ASNI版本,一個Unicode版本。ANSI版本僅執行字符串轉換操作之后調用Unicode版本的函數。
以上介紹的都是windows系統處理Unicode和ANSI字符集的方法。作為一門語言C語言也提供了自己處理它們的機制。C運行庫也提供了不同的函數處理不同的字符集,但是與windows不同的是c運行庫的ANSI版本的函數不會在內部調用Unicode版本的函數。
如strlen用于處理ANSI字符集,返回字符串長度,與之對應的Unicode版本為wcslen。(wide character set)。
C語言提供的對字符串進行修改的函數存在安全隱患,容易造成緩沖區溢出。輕則結果錯誤,重則系統崩潰。因為它們對目標緩沖區進行操作時沒有收到指定緩沖區的最大長度的參數,函數并不知道自己會破壞內存。Microsoft提供了一些新的安全的函數來取代C運行庫不安全的字符串處理函數。雖然我們已經習慣使用strcpy,printf,strcat等這些函數,但是我們還是去要忘記它們,轉而使用windows提供的這些的新的更安全的函數。所有要使用安全的函數程序必須包含StrSafe.h頭文件。現有的每一個函數,如strcpy,_tcscpy都有一個對應的安全新版本函數,strcpy_s,_tcscpy_s。前面的名稱相同但最后添加了一個_s,代表更安全。如老版本的函數的區別是它們需要目標緩沖區的大小。注意不是字符串所占的字節數,而是字符數,無論是ANSI字符還是Unicode字符集都可以使用宏_countof來得到字符數(使用sizeof不行哦,它返回字節數)。如果目標緩沖區不足以容納結果數據,函數就會設置局部于線程的C運行時變量errno。然后返回一個errno_t值來指出成功或失敗。關于安全函數僅僅介紹這么多,一者《windows核心編程》中關于此介紹的很簡單且晦澀難懂。二者平時我們在開發時很少能夠用到,只要知道每個對應的c字符串操作函數都對應著一個安全函數,用時再查也不遲。
現在再也不能將字符認為僅僅占一個字節了,因為存在Unicode的字符。要表示字節可以使用BYTE。建議平時使用通用的數據類型如TCHAR,PTSTR定義變量。同時將字符或字符串用TEXT或_T包括起來。
涉及到ANSI和Unicode字符集的轉換可以使用以下兩個函數:
MultiByteToWideChar和WideCharToMultiByte。
判斷字符串是Unicode還是ANSI字符集可以使用函數IsTextUnicode。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成