zoukankan      html  css  js  c++  java
  • 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。

    编码转换

    UTF-8 Everywhere 文中提供了一个解决方案,在程序中的字符串统一使用 UTF-8 编码并使用 char 或 string 存储而不使用宽字符类型。在需要传入宽字符类型时进行转换,实现 widennarrow 两种类型的函数,完成 UTF-8 和 UTF-16 的互相转换。

    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 函数转换字符串。

    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 字符串,改用该函数控制台正确显示中文字符。

    void print_wstring(const wchar_t *ws) {
    	DWORD w;
    	WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ws, wcslen(ws), &w, NULL);
    }
    
    int main(void) {
    	char cc[] = "xE4xB8x80y
    ";
    	wchar_t utf16[512];
    	wchar_t uu[] = L"一
    ";
    	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进行设置。

  • 相关阅读:
    快速登录机器&数据库
    质量报告之我见
    一些 ssh 小技巧
    virtualenv简介以及一个比较折腾的scrapy安装方法
    用scrapy数据抓取实践
    即将到来的5G,我们该做些什么准备?
    浅谈由管理者角色引出的B端产品设计思考点
    CodeForces 707C Pythagorean Triples (数论)
    UVaLive 6625 Diagrams & Tableaux (状压DP 或者 DFS暴力)
    CodeForces 707B Bakery (水题,暴力,贪心)
  • 原文地址:https://www.cnblogs.com/ljhero/p/4097076.html
Copyright © 2011-2022 走看看