曾经碰到一个问题,
项目需要支持日文操作系统,但是没有编译成unicode程序。
然后在一个解析用户输入路径的地方出问题了。
原因是日文的"表"这个汉字,日文编码格式下,低字节和反斜杠'/'编码一样,解析的时候把它当成路径的分隔符了。:-(
// 項: 8D 80 (Shift_JIS)
// 目: 96 DA (Shift_JIS)
// 表: 95 5C (Shift_JIS)
// /: 5C (Shift_JIS)
反思一下,如果要支持国际化,最正确也最简单的办法是都编译成Unicode程序。
当然这样就很有可能需要进行些字符集的转换,整理了4个函数。
还可以组合使用,比如要多字节字符串-->UTF8格式字符串,可以调用1)+3)。
1)当前系统编码的多字节字符串-->Unicode格式字符串
bool multiToUnicode(const std::string& multiText, std::wstring& unicodeText) { if (0 == multiText.length()) { unicodeText.clear(); return true; } //先获取转换后字符串所需空间 int size = ::MultiByteToWideChar(CP_ACP, 0, multiText.c_str(), -1, NULL, 0); if (0 == size) { return false; } //分配空间,进行转换 wchar_t* wszBuffer = new wchar_t[size + 1]; ::ZeroMemory(wszBuffer, (size + 1) * sizeof(wchar_t)); if (0 == ::MultiByteToWideChar(CP_ACP, 0, multiText.c_str(), -1, wszBuffer, size + 1)) { delete[] wszBuffer; return false; } unicodeText = wszBuffer; delete[] wszBuffer; return true; }
2)Unicode格式字符串-->当前系统编码的多字节字符串
bool unicodeToMulti(const std::wstring& unicodeText, std::string& multiText) { if (0 == unicodeText.length()) { multiText.clear(); return true; } //先获取转换后字符串所需空间 int size = ::WideCharToMultiByte(CP_ACP, 0, unicodeText.c_str(), -1, NULL, 0, NULL, NULL); if (0 == size) { return false; } //分配空间,进行转换 char* szBuffer = new char[size + 1]; ::ZeroMemory(szBuffer, (size + 1) * sizeof(char)); if (0 == ::WideCharToMultiByte(CP_ACP, 0, unicodeText.c_str(), -1, szBuffer, size + 1, NULL, NULL)) { delete[] szBuffer; return false; } multiText = szBuffer; delete[] szBuffer; return true; }
3)Unicode格式字符串-->UTF8格式字符串
bool unicodeToUtf8(const std::wstring& unicodeText, std::string& utf8Text) { if (0 == unicodeText.length()) { utf8Text.clear(); return true; } //先获取转换后字符串所需空间 int size = ::WideCharToMultiByte(CP_UTF8, 0, unicodeText.c_str(), -1, NULL, 0, NULL, NULL); if (0 == size) { return false; } //分配空间,进行转换 char* szBuffer = new char[size + 1]; ::ZeroMemory(szBuffer, (size + 1) * sizeof(char)); if (0 == ::WideCharToMultiByte(CP_UTF8, 0, unicodeText.c_str(), -1, szBuffer, size + 1, NULL, NULL)) { delete[] szBuffer; return false; } utf8Text = szBuffer; delete[] szBuffer; return true; }
4)UTF8格式字符串-->Unicode格式字符串
bool utf8ToUnicode(const std::string& utf8Text, std::wstring& unicodeText) { if (0 == utf8Text.length()) { unicodeText.clear(); return true; } //先获取转换后字符串所需空间 int size = ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.c_str(), -1, NULL, 0); if (0 == size) { return false; } //分配空间,进行转换 wchar_t* wszBuffer = new wchar_t[size + 1]; ::ZeroMemory(wszBuffer, (size + 1) * sizeof(wchar_t)); if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.c_str(), -1, wszBuffer, size + 1)) { delete[] wszBuffer; return false; } unicodeText = wszBuffer; delete[] wszBuffer; return true; }
补充1)
_UNICODE vs UNICODE
在学习UNICODE的过程中发现有两种关于UNICODE的宏定义: UNICODE和_UNICODE.
UNICODE:
这个宏主要是在Windows SKD中使用, 比如GetWindowText(), 定义了UNICODE以后将被定义为GetWindowTextW(UNICODE版本),否则被定义成GetWindowTextA(ANSI版本).
_UNICODE:
该宏一般用在C运行时库和MFC头文件中, 这时候函数_tcslen()将被映射为wcslen(), 反之被映射为strlen(). 至于在MFC中, 一般存在如下的定义:
#ifdef _UNICODE
#ifndef UNICODE
#define UNICODE
#endif
#endif
#ifdef UNICODE
#ifndef _UNICODE
#define _UNICODE
#endif
#endif
所以在MFC使用哪个都可以.
总结:两个都用上总不会有错的:)
补充2)
VC6中为了编译Unicode软件出了需要添加_UNICODE宏定义外,还需要增加一步:指定程序的入口位置。
否则程序将会发生如下错误:error LNK2001: unresolved external symbol _WinMain@16。
解决办法:在Project Settings > Entry-point symbol编辑框中输入wWinMainCRTStartup。
补充3)
C++比Unicode出生得早,所以最开始没有考虑Unicode支持,char类型是单字节的。
wchar_t实际上是typedef定义。
所以特别要注意防止下面错误。
CString sText = _T("123456789");
int nLength = sText.getLength() //i=9
CFile cFile;
......
cFile.Write(sText,sText.getLength());
这段代码再普通不过了,编译也不会有任何问题。但是实际上输出的内容将会是错误的。正确的代码应该如下:
CString sText = _T("123456789");
int nLength = sText.getLength() //i=9
CFile cFile;
......
cFile.Write(sText,sText.getLength()×sizeof(_TCHAR));
补充4)
Java设计的时候已经考虑了对unicode的支持,所以Java的char类型占用2个字节。
Java中如果要进行字符集转换就简单多了,写了个程序测试了一下。
/* * Created on 2011/01/04 * * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Style - Code Templates */ package learning.io; import java.io.UnsupportedEncodingException; import java.util.Locale; // import learning.util.StringToCharSequence; /** * @author * * TODO To change the template for this generated type comment go to Window - * Preferences - Java - Code Style - Code Templates */ public class CharEncodingTest { public static void main(String[] args) throws UnsupportedEncodingException { System.out.println(Locale.getDefault().getDisplayCountry()); System.out.println(System.getProperty("file.encoding")); // /////////////////////////////////////////////////// // Unicode -> UTF8 // 06543210 0xxxxxxx // 110(10)9876 10543210 110xxxxx 10xxxxxx // 1110(15)(14)(13)(12) 10(11)(10)9876 10543210 1110xxxx 10xxxxxx // 10xxxxxx // /////////////////////////////////////////////////// // /////////////////////////////////////////////////// // 項目表\ // 項: unicode, int=38917 \u9805 // 目: unicode, int=30446 \u76EE // 表: unicode, int=34920 \u8868 // \: unicode, int=92 \u005C // // 項: E9 A0 85 (UTF8, 9805 --> E9 A0 85) // 目: E7 9B AE (UTF8, 76EE --> E7 9B AE) // 表: E8 A1 A8 (UTF8, 8868 --> E8 A1 A8) // \: 5C (UTF8, 005C --> 5C) // 項: 8D 80 (Shift_JIS) // 目: 96 DA (Shift_JIS) // 表: 95 5C (Shift_JIS) // \: 5C (Shift_JIS) // /////////////////////////////////////////////////// // /////////////////////////////////////////////////// // 项目表\ // 项: unicode, int=39033 \u9879 // 目: unicode, int=30446 \u76EE // 表: unicode, int=34920 \u8868 // \: unicode, int=92 \u005C // // 项: E9 A1 B9 (UTF8, 9879 --> E9 A1 B9) // 目: E7 9B AE (UTF8, 76EE --> E7 9B AE) // 表: E8 A1 A8 (UTF8, 8868 --> E8 A1 A8) // \: 5C (UTF8, 005C --> 5C) // 项: CF EE (GBK) // 目: C4 BF (GBK) // 表: B1 ED (GBK) // \: 5C (GBK) // /////////////////////////////////////////////////// parseString("項目表\\"); // "ABAB" String strUTF8 = String.valueOf(new char[] {0xEF, 0xBC, 0xA1, 0xEF, 0xBC, 0xA2, 0x41, 0x42}); String strJP = String.valueOf(new char[] {0x82, 0x60, 0x82, 0x61, 0x41, 0x42}); String strCN = String.valueOf(new char[] {0xA3, 0xC1, 0xA3, 0xC2, 0x41, 0x42}); // "UTF8" -> "Unicode" String strUnicode1 = new String(strUTF8.getBytes("ISO-8859-1"), "UTF8"); // "Shift_JIS" -> "Unicode" String strUnicode2 = new String(strJP.getBytes("ISO-8859-1"), "Shift_JIS"); // "GBK" -> "Unicode" String strUnicode3 = new String(strCN.getBytes("ISO-8859-1"), "GBK"); System.out.println(strUnicode1); System.out.println(strUnicode2); System.out.println(strUnicode3); } public static void parseString(String strTest) throws UnsupportedEncodingException { printCharArray(strTest); System.out.println("UTF8:"); byte a[] = strTest.getBytes("UTF8"); printByteArray(a); System.out.println("Shift_JIS:"); byte b[] = strTest.getBytes("Shift_JIS"); printByteArray(b); System.out.println("GBK:"); byte c[] = strTest.getBytes("GBK"); // Encode into "GBK" printByteArray(c); } public static void printByteArray(byte[] bytes) { for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i]).toUpperCase(); StringBuffer sb = new StringBuffer(); // print byte sb.append("byte["); sb.append(i); sb.append("]='"); sb.append(bytes[i]); sb.append("'\t"); // short value sb.append(hex); sb.append('\t'); System.out.println(sb.toString()); } System.out.println(); } public static void printCharArray(String inStr) { char[] myBuffer = inStr.toCharArray(); // list each Charactor in byte value, short value, // and UnicodeBlock Mapping for (int i = 0; i < inStr.length(); i++) { int s = (int) myBuffer[i]; String hexS = Integer.toHexString(s).toUpperCase(); StringBuffer sb = new StringBuffer(); // print char sb.append("char["); sb.append(i); sb.append("]='"); sb.append(myBuffer[i]); sb.append("'\t"); // int value sb.append("int="); sb.append(s); sb.append(" \\u"); sb.append(hexS); sb.append('\t'); // Unicode Block sb.append(Character.UnicodeBlock.of(myBuffer[i])); System.out.println(sb.toString()); } System.out.println(); } }