移動開發(fā)之淺析cocos2d-x的中文支持問題 題記:這陣子一直在學(xué)習(xí)cocos2d-x,,其跨平臺的特性確實(shí)讓人舒爽,引擎的框架概念也很成熟,,雖然相應(yīng)的第三方工具略顯單薄,,但也無愧是一件移動開發(fā)的利器啊,,有興趣的朋友有時(shí)間就多了解一下吧 :) 使用引擎的過程中遇到的問題很多,中文支持便是一例,,雖然問題本身并不復(fù)雜,,但是網(wǎng)上的相關(guān)資料都比較簡單零散,自己搗鼓了幾下,,覺得有必要的整理一下,,以此稍稍方便一下遇到相似問題的朋友,也算是自己的一點(diǎn)點(diǎn)學(xué)習(xí)總結(jié),,可惜自己水平有限,,講的不當(dāng)甚至錯誤的地方請細(xì)心的朋友不吝指出,俗話說的好嘛:貽笑大方事小,,誤人子弟事大也 :) 一. 不就是用用setlocale嘛,,簡單! 之前自己并未深入過C/C++對于多字符編碼的支持問題,,但也算是嘗試過一些相關(guān)的示例程序,,譬如這個(gè)中文版的Hello,World: #include <cstdio> #include <clocale> using namespace std; int main() { // first we have to set the locale setlocale(LC_ALL, "chs"); // then we use wprintf to output the Chinese string wprintf(L"%ls\n", L"世界,,你好"); // that's all :) return 0; } 程序的邏輯很簡單,,使用setlocale設(shè)置地域信息,然后使用wprintf輸出對應(yīng)的中文信息,,一切似乎都非常簡單自然,。(注:如果使用GCC編譯上述代碼時(shí),需要將源文件類型改為UTF-8格式,,否則會提示解碼錯誤,,這是GCC的一個(gè)源文件限制) 類似的,這里也有一個(gè)相同功能的C++程序: #include <iostream> using namespace std; int main() { // first we have to set the output stream locale wcout.imbue(locale("chs")); // then we use wcout to output the Chinese string wcout << L"世界,你好" << endl; // that's all :) return 0; } 代碼的原理部分與之前的代碼如出一轍,,在此不再贅述,,不過比較令我費(fèi)解的是這段代碼在CodeBlocks+MinGW4.4.1環(huán)境下,運(yùn)行時(shí)會引起崩潰(由wcout.imbue(locale("chs"))這行代碼引起),,具體原因沒有細(xì)究,,似乎是GCC的一個(gè)BUG,如有朋友了解,,請不吝告知,,多謝 :) 關(guān)于以上問題的更多的一些信息可以在這里找到。 好了,,小小嘗試了一下之后,,就讓我們開始整一整cocos2d-x中的中文了,巧的是cocos2d-x開發(fā)包(有興趣的朋友可以從這里下載)的示例程序中恰好也有一個(gè)HelloWorld,,那么事不宜遲,,就讓我們將這個(gè)程序修改為中文界面吧 :) 二. char 或者 wchar_t,這是一個(gè)問題 首先讓我們來看看cocos2d-x中原始的HelloWorld里涉及到的文本輸出代碼是哪些(如果你從未接觸過cocos2d,,推薦先熟悉一下其中的基本概念,,網(wǎng)上優(yōu)秀的入門教程很多,譬如這里): ///////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label CCLabelTTF* pLabel = CCLabelTTF::labelWithString("Hello World", "Arial", 24); // ask director the window size CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the label on the center of the screen pLabel->setPosition( ccp(size.width / 2, size.height - 50) ); // add the label as a child to this layer this->addChild(pLabel, 1); 注釋都很明了,,大致意思便是創(chuàng)建一個(gè)CCLabelTTF,,然后設(shè)置對應(yīng)位置,并加入場景的child隊(duì)列,,就這樣~~~在此給張示意例圖: OK,,到此我們的當(dāng)前任務(wù)已經(jīng)非常明顯了,就是要將圖中所示的“Hello World”改為“你好世界”,,就是這么簡單 :) 簡單,,讓我們首先來一次最直接的修改: ///////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label CCLabelTTF* pLabel = CCLabelTTF::labelWithString(L"你好世界", "Arial", 24); // ask director the window size CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the label on the center of the screen pLabel->setPosition( ccp(size.width / 2, size.height - 50) ); // add the label as a child to this layer this->addChild(pLabel, 1); 想法雖然美好,可惜編譯器無情的抱怨了:沒有對應(yīng)的調(diào)用接口……讓我們來看一下labelWithString這個(gè)靜態(tài)函數(shù)的聲明: /** creates a CCLabelTTF from a fontname, alignment, dimension and font size */ static CCLabelTTF * labelWithString(const char *label, const CCSize& dimensions, CCTextAlignment alignment, const char *fontName, float fontSize); /** creates a CCLabelTTF from a fontname and font size */ static CCLabelTTF * labelWithString(const char *label, const char *fontName, float fontSize); 我們暫時(shí)不用細(xì)究labelWithString的第一個(gè)重載版本,,其實(shí)現(xiàn)了字體顯示的更多細(xì)節(jié)控制,,我們目前關(guān)心的是在源程序中我們調(diào)用的第二個(gè)版本,非常明顯的一點(diǎn)便是其第一個(gè)文本參數(shù),,是一個(gè)常量char指針,,而我們之前做的簡單修改,實(shí)際上是傳入了一個(gè)常量wchar_t指針,,自然便出現(xiàn)了重載函數(shù)不存在的情況……那么接下來我們應(yīng)該如何做呢,?將wchar_t強(qiáng)制轉(zhuǎn)換為char類型,恐怕沒有這么簡單,,考慮ASCII字符A,,其相應(yīng)的wchar_t內(nèi)容編碼可能是這樣的0x00|0x41,,將其強(qiáng)制轉(zhuǎn)換為char之后,其相應(yīng)內(nèi)容其實(shí)并沒有改變,,傳給labelWithString只是相當(dāng)于一個(gè)空字符串而已……那么讓我們來修改labelWithString以讓他支持wchar_t的調(diào)用或者自己動手?jǐn)U展類似的接口,,誠然,以上方案都是可行的,,但是都不是那么簡潔…… 唉,char 或者 wchar_t,,這是一個(gè)問題啊~~~ 三. 擁抱UTF-8 其實(shí),,我們還有更簡單的方案,那就是使用UTF-8編碼,,在此讓我簡單的引用一段維基百科上關(guān)于UTF-8的簡介: UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼(定長碼),,也是一種前綴碼。它可以用來表示Unicode標(biāo)準(zhǔn)中的任何字符,,且其編碼中的第一個(gè)字節(jié)仍與ASCII相容,,這使得原來處理ASCII字符的軟件無須或只須做少部份修改,即可繼續(xù)使用,。 看到了UTF-8編碼的重要特性之一嗎:與ASCII兼容,!這就是我們想要的 :) 那么事不宜遲,讓我們馬上動手將L“你好世界”轉(zhuǎn)換為UTF-8編碼,,并傳入labelWithString試一試吧: 等等,,在轉(zhuǎn)碼之前也許你會問:不管UTF-8本身的編碼方式如何,之前我們硬編碼進(jìn)程序中的L“你好世界”這幾個(gè)字符原來是什么編碼呢,?如果我們連原始編碼都不了解,,談何轉(zhuǎn)碼一說呢?呵呵,,你說的非常正確,!只可惜關(guān)于C++中使用什么編碼方式存儲多字節(jié)字符并沒有統(tǒng)一規(guī)定,各個(gè)編譯器期間都有區(qū)別,,可能是UTF-16,,可能是UTF-32,當(dāng)然,,也有可能是UTF-8(這種情況下強(qiáng)制轉(zhuǎn)換就可行了,,盡管仍然很Ugly……),不過幸運(yùn)的是,,Win32的API :WideCharToMultiByte為我們屏蔽了這些復(fù)雜性,,讓我們馬上來試一試: inline std::string WideByte2UTF8(const wstring& text) { int asciisize = ::WideCharToMultiByte(CP_UTF8, 0, text.c_str(), text.size(), NULL, 0, NULL, NULL); if (asciisize == ERROR_NO_UNICODE_TRANSLATION || asciisize == 0) { return string(); } char* resultstring = new char[asciisize]; int convresult = ::WideCharToMultiByte(CP_UTF8, 0, text.c_str(), text.size(), resultstring, asciisize, NULL, NULL); if (convresult != asciisize) { return string(); } std::string buffer(resultstring, convresult); delete[] resultstring; return buffer; }
// add a label shows "Hello World" // create and initialize a label std::string text = WideByte2UTF8(L"你好世界"); CCLabelTTF* pLabel = CCLabelTTF::labelWithString(text.c_str(), "Arial", 24); 哈哈,我們的第一步嘗試成功了,,來看一下截圖: 四. 還有其他更好的法子嗎,? 上面的代碼雖然可以完成任務(wù),,但是用到了平臺特有的API,對于像cocos2d-x這般旨在跨平臺的引擎而言,,其實(shí)并不能算作很好的解決方案,,那么我們還有什么其他法子嗎? 1.wcstombs/mbstowcs怎么樣,? 其實(shí)C函數(shù)庫中存在類似于WideCharToMultiByte的函數(shù),,他們就是wcstombs/mbstowcs,讓我們馬上來試一試: //! convert wide string to string inline std::string WStr2Str(const std::wstring& ws) { std::string curLocale = setlocale(LC_ALL, NULL); // get current locale setlocale(LC_ALL, "chs"); const wchar_t* tSource = ws.c_str(); size_t tDsize = 2 * ws.size() + 1; char* tDest = new char[tDsize]; memset(tDest,0,tDsize); wcstombs(tDest,tSource,tDsize); std::string result = tDest; delete[] tDest; setlocale(LC_ALL, curLocale.c_str()); return result; } // add a label shows "Hello World" // create and initialize a label std::string text = WStr2Str(L"你好世界"); CCLabelTTF* pLabel = CCLabelTTF::labelWithString(text.c_str(), "Arial", 24);
不幸的是以上方案失敗了:
失敗的原因其實(shí)很簡單,,wcstombs是根據(jù)當(dāng)前設(shè)置的locale進(jìn)行轉(zhuǎn)換,,由于此處我們設(shè)置的locale是“chs”,所以裝換的過程是根據(jù)GBK編碼進(jìn)行的(就簡體中文而言),,并非是我們理想中的UTF-8,,自然得不到我們期望的結(jié)果,那么為什么我們此處不設(shè)置locale為UTF-8呢,,這是因?yàn)椋?/span>Windows CRT 不支持~~~(唉……),,但是我們至此可以找到一個(gè)這種方案:通過條件編譯實(shí)現(xiàn)對應(yīng)函數(shù)的裝換,即在Win32平臺上調(diào)用WideCharToMultiByte,,其他支持UTF-8 locale的平臺則調(diào)用wcstombs,,嗯哼,貌似這個(gè)方案部分做到了跨平臺的特性(對于那些不支持UTF-8 locale的非Win32平臺還是不行~~~) :) 2. Boost提供的 utf8_codecvt_facet 怎么樣,? Boost中提供的utf8_codecvt_facet可以完成類似的UTF-8編碼轉(zhuǎn)換,,可惜作為標(biāo)準(zhǔn)IO庫中codecvt 的一個(gè)擴(kuò)展,與string/wstring協(xié)作時(shí)并不是那么直觀,,有興趣的朋友可以試上一試,,個(gè)人感覺不是特別好 :( 3. 第三庫如何? 其實(shí)cocos2d-x的開發(fā)包內(nèi)置了用于編碼轉(zhuǎn)換的iconv庫,,我們可以嘗試一下,,當(dāng)然,更加重量級的還有ICU,,如果你有興趣的也可以研習(xí)一番,,在此我們便就近試一下iconv吧: bool IConvConvert(const char *from_charset, const char *to_charset, const char *inbuf, int inlen, char *outbuf, int outlen) { iconv_t cd = iconv_open(to_charset, from_charset); if (cd == 0) return false; const char **pin = &inbuf; char **pout = &outbuf; memset(outbuf,0,outlen); size_t ret = iconv(cd,pin,(size_t *)&inlen,pout,(size_t *)&outlen); iconv_close(cd); return ret == (size_t)(-1) ? false : true; } std::string IConvConvert_GBKToUTF8(const std::string& str) { const char* textIn = str.c_str(); char textOut[256]; bool ret = IConvConvert("gb2312", "utf-8", textIn, strlen(textIn),textOut, 256); return ret ? string(textOut) : string(); } // add a label shows "Hello World" // create and initialize a label std::string text = IConvConvert_GBKToUTF8("你好世界"); CCLabelTTF* pLabel = CCLabelTTF::labelWithString(text.c_str(), "Arial", 24); 哈哈,這次我們成功了:
不過iconv的GNU許可倒是值得考量一下,,對于一些用戶可能產(chǎn)生困擾,,當(dāng)然如果考慮清楚,并且程序規(guī)模不存在問題時(shí),,我們還是推薦iconv的,,否則cocos2d-x也不會自帶iconv了 :) 4. 自給自足,豐衣足食 雖然軟件行業(yè)從不提倡重造車輪,,但是如果你還不滿意上述的各類方法,,那么這也算是一條路子了,,優(yōu)點(diǎn)是夠輕量、夠可控等等,;缺點(diǎn)同樣明顯,,很脆弱、花時(shí)間等等,,當(dāng)然如果你確定考慮清楚了,,那就放手干吧,這里給個(gè)示例示例: //! convert from wstring to UTF8 using self-coding-converting inline void WStrToUTF8(std::string& dest, const wstring& src){ dest.clear(); for (size_t i = 0; i < src.size(); i++){ wchar_t w = src[i]; if (w <= 0x7f) dest.push_back((char)w); else if (w <= 0x7ff){ dest.push_back(0xc0 | ((w >> 6)& 0x1f)); dest.push_back(0x80| (w & 0x3f)); } else if (w <= 0xffff){ dest.push_back(0xe0 | ((w >> 12)& 0x0f)); dest.push_back(0x80| ((w >> 6) & 0x3f)); dest.push_back(0x80| (w & 0x3f)); } else if (sizeof(wchar_t) > 2 && w <= 0x10ffff){ dest.push_back(0xf0 | ((w >> 18)& 0x07)); // wchar_t 4-bytes situation dest.push_back(0x80| ((w >> 12) & 0x3f)); dest.push_back(0x80| ((w >> 6) & 0x3f)); dest.push_back(0x80| (w & 0x3f)); } else dest.push_back('?'); } } //! simple warpper inline std::string WStrToUTF8(const std::wstring& str){ std::string result; WStrToUTF8(result, str); return result; } // add a label shows "Hello World" // create and initialize a label std::string text = WStrToUTF8(L"你好世界"); CCLabelTTF* pLabel = CCLabelTTF::labelWithString(text.c_str(), "Arial", 24); 情況OK :)
5. 還有其他法子,? 目前我能想到的法子就這么多了,,什么,你還有其他法子,,好吧,盡量曬出來吧,,讓我也開開眼界 :) 五. 最后說幾句 Cocos2d-x的中文支持到此也算聊完了,,有興趣的朋友可以看看,以上內(nèi)容也算是自己的一些學(xué)習(xí)歷程,,在此一一列出,,希望能給朋友一些借鑒 :) That's All :) 部分參考(排名部分先后): 1.http:///questions/148403/utf8-to-from-wide-char-conversion-in-stl 2.http://www./doc/libs/1_48_0/libs/serialization/doc/codecvt.html 3.http://www.cnblogs.com/hnrainll/archive/2011/05/07/2039700.html 4.http://www./software/libiconv/ 6.http://blog.csdn.net/sjy88813/article/details/6662879 7.http://www./questions/programming-9/wstring-utf8-conversion-in-pure-c-701084/ |
|