Windows 程序支持 Unicode

寬字符

閱讀了 UTF-8 Everywhere 一文,推薦在程序中對於字符串都使用 UTF-8 編碼。Unix-like 系統默認是支持 UTF-8 編碼的Unicode字符串,標準庫函數也默認支持 UTF-8 字符串,如 fopen 等。但在 Windows 系統,因爲歷史緣由,其對須要輸入寬字符的函數提供了另外以 w 開頭的標準庫擴展函數,如 _wfopen 等。何況對標準庫的 wchar_t 兩種系統實現不同,在 unix-like 系統中是佔4字節的 UTF-8 編碼,而在 Windows 系統中是佔2字節的 UTF-16 編碼。Windows 不少系統 API 接受 wchar_t 類型的字符串,這就須要把 UTF-8 編碼的字符串轉換爲 UTF-16。html

編碼轉換

UTF-8 Everywhere 文中提供了一個解決方案,在程序中的字符串統一使用 UTF-8 編碼並使用 char 或 string 存儲而不使用寬字符類型。在須要傳入寬字符類型時進行轉換,實現 widennarrow 兩種類型的函數,完成 UTF-8 和 UTF-16 的互相轉換。windows

std::string narrow(const wchar_t *s);
std::wstring widen(const char *s);
std::string narrow(const std::wstring &s);
std::wstring widen(const std::string &s);

wchar_t *widen(const char *s);
char *narrow(const wchar_t *s);

在調用須要傳入寬字符串的 Windows API時,使用 widen 函數轉換字符串。ide

CopyFileW(widen(existing_file).c_str(), 
            widen(new_file).c_str(),
            TRUE);

函數實現

Boost.Nowide 中,包含 widennarrow 兩種類型函數的實現,並對標準庫函數進行了包裝,使得能夠編寫跨平臺支持 Unicode 的程序。函數

UTF-8 Everywhere 中也提到能夠使用 Windows 的 MultiByteToWideCharWideCharToMultiByte 兩個 API 實現兩個轉換函數。測試

#include <windows.h>

wchar_t *widen(const char *s, wchar_t *ws, size_t ws_size) {
    size_t required_size;

    // Get the required buffer size
    required_size = MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, 0);

    if (required_size >= ws_size)
        return NULL;

    // Convert NULL terminated UTF-8 string to the UTF-16 (wide character) string
    if (MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, ws_size) == 0)
        return NULL;

    return ws;
}

char *narrow(const wchar_t *ws, char *s,  size_t s_size) {
    size_t required_size;

    // Get the required buffer size
    required_size = WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, 0, NULL, NULL);

    if (required_size >= s_size)
        return NULL;

    // Convert NULL terminated UTF-8 string to the UTF-16 (wide character) string
    if (WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, s_size, NULL, NULL) == 0)
        return NULL;

    return s;
}

寫代碼測試兩個函數時,遇到了控制檯輸出亂碼問題。UTF-8 字符串轉換爲 wchar_t 類型字符串以後應該就能使用 wprintf 函數輸出,但實際只有英文字符能正常輸出,中文就是亂碼。這主要時由於控制檯使用的編碼方式不是 Unicode, 中文的系統默認是 GBK,而寬字符輸出的是 UTF-16,這中間就存在編碼轉換的問題,庫函數 wprintf 沒有自動轉換。查看 Boost.Nowide 對 Console I/O 的實現說明,其利用的是 ReadConsoleW/WriteConsoleW 系統 API。WriteConsoleW 支持輸出 Unicode 字符串,改用該函數控制檯正確顯示中文字符。ui

void print_wstring(const wchar_t *ws) {
    DWORD w;
    WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ws, wcslen(ws), &w, NULL);
}

int main(void) {
    char cc[] = "\xE4\xB8\x80y\n";
    wchar_t utf16[512];
    wchar_t uu[] = L"一\n";
    print_wstring(widen(cc, utf16, sizeof(utf16)));
    print_wstring(uu);
    system("pause");
    return 0;
}

源代碼中的 UTF-8 字符串非 ASCII 字符直接使用16進製表示,wchar_t 類型的能夠直接輸入,但源代碼文件使用的編碼方式要支持Unicode 編碼。編譯器會自動根據源代碼文件的編碼方式解碼字符串並使用 wchar_t 類型的編碼方式編碼字符串存儲在最終編譯生成的可執行文件中,在 Windows 系統中就是 UTF-16。爲了不沒必要要的編碼問題,源代碼文件也統一使用 UTF-8 編碼保存,不過 visual studio 要使用帶 BOM 的 UTF-8,不帶 BOM 的不能正確識別。vs 2010 中打開 File -> Adavanced Save Options進行設置。編碼

相關文章
相關標籤/搜索