【轉】VC下的Unicode編程


轉自http://www.leewei.org/?p=1304

UniCode簡述

    在Windows下用VC編程,如果編寫的程序要在多種語言環境下運行(比如日文、中文、葡萄牙文等),使用VC默認的MBCS編譯選項就會出現亂碼,甚至導致程序崩潰。要克服這一缺點,就需要使用Unicode編程,簡要說明一下Unicode:

    Unicode也是一種字符編碼方法,它占用兩個字節(0000H—FFFFH),容納65536個字符,這完全可以容納全世界所有語言文字的編碼。在Unicode里,所有的文字都按一個字符來處理,它們都有一個唯一的Unicode碼。

    Windows NT及后續系統的內核都是基於Unicode的。在Windows內核中,宏UNICODE指示是否啟用Unicode,而C++是根據_UNICODE宏來判斷的,因此在編程中我們要把這兩個宏寫進預處理參數里。

    比如在tchar.h頭文件中,有如下聲明:

#define _T(x)       __T(x)
 
#ifdef  _UNICODE
typedef wchar_t     TCHAR;
#define __T(x)      L##x
#else
typedef char        TCHAR;
#define __T(x)      x
#endif

    而在winnt.h頭文件中,定義了如下數據類型:

typedef char CHAR, *LPSTR; 
typedef CONST CHAR *LPCSTR, *PCSTR; 
 
typedef unsigned short WCHAR,*LPWSTR;    // 16-bit UNICODE character
typedef CONST WCHAR *LPCWSTR, *PCWSTR;
 
//
#ifdef  UNICODE 
typedef WCHAR TCHAR, *PTCHAR;
typedef LPWSTR LPTCH, PTCH;
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR LPCTSTR;
#define __TEXT(quote) L##quote 
#else       
typedef char TCHAR, *PTCHAR;
typedef LPSTR LPTCH, PTCH;
typedef LPSTR PTSTR, LPTSTR;
typedef LPCSTR LPCTSTR;
#define __TEXT(quote) quote        
#endif /* UNICODE */

    實際上Win32 API有兩個版本。一個版本接受MBCS字符串,另一個接受Unicode字符串。例如:其實根本沒有SetWindowText()這個API函數,相反,有SetWindowTextA()和SetWindowTextW()。后綴A表明這是MBCS函數,后綴W表示這是Unicode版本的函數。這些API函數的頭文件在winuser.h中聲明,下面例舉winuser.h中的SetWindowText()函數的聲明部分:

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

UniCode實戰

    在VC6.0下使用Unicode的步驟如下:
    1、project->Settings…->C/C++->Preprocessor Definitions,刪除_MBCS,然后添加_UNICODE,UNICODE。
    2、project->Settings…->Link->Category選擇Output,Entry-point Symbol欄填入wWinMainCRTStartup。

    【注】如果是不是exe工程(比如DLL或LIB),不執行第二個步驟,否則會出現warning LNK4086錯誤。

    C++使用wchar_t來表示一個寬字符,它在內部被定義為unsigned short,占兩個字節。相對於普通字符,C++有一整套的寬字符操縱函數, 以下是一份寬字符處理函數函數與普通函數對照表:

寬字符處理函數函數與普通函數對照表
字符分類:
寬字符函數    普通C函數    描述
iswalnum()    isalnum()    測試字符是否為數字或字母
iswalpha()    isalpha()    測試字符是否是字母
iswcntrl()    iscntrl()    測試字符是否是控制符
iswdigit()    isdigit()    測試字符是否為數字
iswgraph()    isgraph()    測試字符是否是可見字符
iswlower()    islower()    測試字符是否是小寫字符
iswprint()    isprint()    測試字符是否是可打印字符
iswpunct()    ispunct()    測試字符是否是標點符號
iswspace()    isspace()    測試字符是否是空白符號
iswupper()    isupper()    測試字符是否是大寫字符
iswxdigit()    isxdigit()    測試字符是否是十六進制的數字
 
大小寫轉換:
寬字符函數    普通C函數    描述
towlower()    tolower()    把字符轉換為小寫
towupper()    toupper()    把字符轉換為大寫
 
字符比較:
寬字符函數    普通C函數    描述
wcscoll()    strcoll()    比較字符串
 
日期和時間轉換:
寬字符函數    描述
strftime()            根據指定的字符串格式和locale設置格式化日期和時間
wcsftime()            根據指定的字符串格式和locale設置格式化日期和時間, 並返回寬字符串
strptime()            根據指定格式把字符串轉換為時間值, 是strftime的反過程
 
打印和掃描字符串:
寬字符函數            描述
fprintf()/fwprintf()    使用vararg參量的格式化輸出
fscanf()/fwscanf()        格式化讀入
printf()            使用vararg參量的格式化輸出到標准輸出
scanf()            從標准輸入的格式化讀入
sprintf()/swprintf()    根據vararg參量表格式化成字符串
sscanf()            以字符串作格式化讀入
vfprintf()/vfwprintf()    使用stdarg參量表格式化輸出到文件
vprintf()            使用stdarg參量表格式化輸出到標准輸出
vsprintf()/vswprintf()    格式化stdarg參量表並寫到字符串
 
數字轉換:
寬字符函數    普通C函數    描述
wcstod()    strtod()    把寬字符的初始部分轉換為雙精度浮點數
wcstol()    strtol()    把寬字符的初始部分轉換為長整數
wcstoul()    strtoul()    把寬字符的初始部分轉換為無符號長整數
 
多字節字符和寬字符轉換及操作:
寬字符函數            描述
mblen()            根據locale的設置確定字符的字節數
mbstowcs()            把多字節字符串轉換為寬字符串
mbtowc()/btowc()        把多字節字符轉換為寬字符
wcstombs()            把寬字符串轉換為多字節字符串
wctomb()/wctob()        把寬字符轉換為多字節字符
 
輸入和輸出:
寬字符函數    普通C函數    描述
fgetwc()    fgetc()    從流中讀入一個字符並轉換為寬字符
fgetws()    fgets()    從流中讀入一個字符串並轉換為寬字符串
fputwc()    fputc()    把寬字符轉換為多字節字符並且輸出到標准輸出
fputws()    fputs()    把寬字符串轉換為多字節字符並且輸出到標准輸出串
getwc()    getc()    從標准輸入中讀取字符, 並且轉換為寬字符
getwchar()    getchar()    從標准輸入中讀取字符, 並且轉換為寬字符
None        gets()    使用fgetws()
putwc()    putc()    把寬字符轉換成多字節字符並且寫到標准輸出
putwchar()    getchar()    把寬字符轉換成多字節字符並且寫到標准輸出
None        puts()    使用fputws()
ungetwc()    ungetc()    把一個寬字符放回到輸入流中
 
字符串操作:
寬字符函數    普通C函數    描述
wcscat()    strcat()    把一個字符串接到另一個字符串的尾部
wcsncat()    strncat()    類似於wcscat(), 而且指定粘接字符串的粘接長度.
wcschr()    strchr()    查找子字符串的第一個位置
wcsrchr()    strrchr()    從尾部開始查找子字符串出現的第一個位置
wcspbrk()    strpbrk()    從一字符字符串中查找另一字符串中任何一個字符第一次出現的位置
wcswcs()/wcsstr() strchr()在一字符串中查找另一字符串第一次出現的位置
wcscspn()    strcspn()    返回不包含第二個字符串的的初始數目
wcsspn()    strspn()    返回包含第二個字符串的初始數目
wcscpy()    strcpy()    拷貝字符串
wcsncpy()    strncpy()    類似於wcscpy(), 同時指定拷貝的數目
wcscmp()    strcmp()    比較兩個寬字符串
wcsncmp()    strncmp()    類似於wcscmp(), 還要指定比較字符字符串的數目
wcslen()    strlen()    獲得寬字符串的數目
wcstok()    strtok()    根據標示符把寬字符串分解成一系列字符串
wcswidth()    None        獲得寬字符串的寬度
wcwidth()    None        獲得寬字符的寬度
 
另外還有對應於memory操作的 wmemcpy(),wmemchr(),wmemcmp(),wmemmove(),wmemset()。

    Unicode編程中,如果需要聲明一個寬字符串,需要這樣寫:
    wchar_t *wstr = L”Hello”;
    其中字符”L”告訴編譯器你要構造的是一個寬字符串,”L”和字符串之間不能有空格。

    雖然上述聲明字符串的代碼是正確的,但是並不提倡這樣做,因為程序可移植性太差。
    還記得前面介紹的幾個宏么?_T(x)會在_UNICODE定義了的情況下被擴展為L##x, 而在一般情況下被擴展為x;TCHAR則分別被替換為wchar_t和char。因此我們可以這樣寫:
    TCHAR *str = _T(“Hello”);
    這樣,如果_UNICODE宏被定義了,則它被擴展為:
    wchar_t *wstr = L”Hello”;
    否則,在默認情況下被擴展為:
    char *str = “Hello”;

    如果需要寫一個庫,而且要分別提供Unicode和非Unicode版本,那么僅僅許多修改兩個UNICODE宏就可以了,不需要修改任何代碼。

遷移到Unicode

    如果非常不幸,你的項目在一開始沒有被設計為使用Unicode(沒有使用_T()宏和TCHAR等類型),而現在出於國際化的需要要使其支持Unicode,那么在添加兩個UNICODE宏和函數入口點后會可能會出現無數個編譯錯誤(我遇到過566個的)。雖然修改的方式根據項目而不同,但也多少有點相似之處,有步驟地做總比漫無目的得改好。

    1、搜索所有的AfxMessagebox和Messagebox函數,將其中的字符串加上_T()宏。
    2、搜索所有的str.Format函數,為第一個參數加上_T()宏。
    3、為字符串常量加上_T()宏。
    4、將strlen、strcpy等函數替換為wcslen、wcscpy等寬字符版本。
    5、如果wcsncpy、wcsncmp等函數的第三個參數是sizeof(dst),那么現在就要改為sizeof(dst)/2,或者自定義一個宏tsizeof來實現。
    6、如果某個函數確實需要char*等類型的參數,使用T2A()宏對參數進行轉換,並在所在函數開頭添加”USES_CONVERSION;”。
    7、查找所有的char* p = (LPSTR)(LPCTSTR)CString這樣的強制轉換代碼,並用char *p = T2A(CString);代替。

    通常修改完以上內容,再次編譯時錯誤應該減少了大半了,現在再一個一個地對照修改就容易多了。

    最后,配置文件也要存儲為Unicode的形式。Unicode的文件頭有個0xFEFF標識,如果你是通過::WritePrivateProfileString()來寫入配置文件的,那么只需要在調用此API之前往文件里寫入0xFEFF文件頭,此后WritePrivateProfileString會自動將后續內容保存成Unicode的形式。為了簡單,可以講程序中調用的::WritePrivateProfileString()全都替換成如下改寫版本即可:

static BOOL _WritePrivateProfileString(LPCTSTR lpAppName, // section name
                   LPCTSTR lpKeyName, // key name
                   LPCTSTR lpString,   // string to add
                   LPCTSTR lpFileName // initialization file
                   )
{
    FILE *fp;
    fp = _tfopen(lpFileName, _T("r"));
    if (fp == NULL)
    {
        fp=_tfopen(lpFileName, _T("w+b"));
 
        wchar_t m_strUnicode[1];
        m_strUnicode[0] = wchar_t(0XFEFF);
        fputwc(*m_strUnicode,fp);
    }
    fclose(fp);
 
    return ::WritePrivateProfileString(lpAppName, lpKeyName, lpString, lpFileName);
}

 


免責聲明!

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



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