曾经碰到一个问题,
项目需要支持日文操作系统,但是没有编译成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();
}
}