JNI和C++通信中文亂碼的問(wèn)題
首先,需要明確幾個(gè)關(guān)于編碼的基本概念:
◆ java內(nèi)部是使用的16bit的unicode編碼(utf-16)來(lái)表示字符串的,無(wú)論英文還是中文都是2字節(jié);
◆ jni內(nèi)部是使用utf-8編碼來(lái)表示字符串的,utf-8是變長(zhǎng)編碼的unicode,一般ascii字符是1字節(jié),中文是3字節(jié);
◆ c/c++使用的是原始數(shù)據(jù),ascii就是一個(gè)字節(jié),中文一般是GB2312編碼,用2個(gè)字節(jié)表示一個(gè)漢字。
jni的中文字符串處理
先從字符流的方向分別對(duì)java-->C++和C++-->java進(jìn)行分析
◆ java-->C++
這種情況下,java調(diào)用的時(shí)候使用的是utf-16編碼的字符串,jvm把這個(gè)參數(shù)傳遞給jni,C++得到的輸入是jstring,此時(shí),可以利用jni提供的兩種函數(shù),一個(gè)是GetStringUTFChars,這個(gè)函數(shù)將得到一個(gè)UTF-8編碼的字符串;另一個(gè)是GetStringChars這個(gè)將得到UTF-16編碼的字符串。無(wú)論那個(gè)函數(shù),得到的字符串如果含有中文,都需要進(jìn)一步轉(zhuǎn)化成GB2312的編碼。
- String
- (UTF-16)
- |
- [java] |
- -------------------- JNI 調(diào)用
- [cpp] |
- v
- jstring
- (UTF-16)
- |
- +--------+---------+
- |GetStringChars |GetStringUTFChars
- | |
- v v
- wchar_t* char*
- (UTF_16) (UTF-8)
◆ c/c++ –> java
jni返回給java的字符串,c/c++首先應(yīng)該負(fù)責(zé)把這個(gè)字符串變成UTF-8或者UTF-16格式,然后通過(guò)NewStringUTF或者NewString來(lái)把它封裝成jstring,返回給java就可以了。
- String
- (UTF-16)
- ^
- |
- [java] |
- -------------------- JNI 返回
- [cpp] | jstring
- (UTF-16)
- ^
- |
- +--------+---------+
- ^ ^
- | |
- |NewString |NewStringUTF
- wchar_t* char*
- (UTF_16) (UTF-8)
如果字符串中不含中文字符,只是標(biāo)準(zhǔn)的ascii碼,那么用GetStringUTFChars/NewStringUTF就可以搞定了,因?yàn)檫@種情況下,UTF-8編碼和ascii編碼是一致的,不需要轉(zhuǎn)換。
但是如果字符串中有中文字符,那么在c/c++部分進(jìn)行編碼轉(zhuǎn)換就是一個(gè)必須了。我們需要兩個(gè)轉(zhuǎn)換函數(shù),一個(gè)是把UTF8/16的編碼轉(zhuǎn)成GB2312;一個(gè)是把GB2312轉(zhuǎn)成UTF8/16。
這里要說(shuō)明一下:linux和win32都支持wchar,這個(gè)事實(shí)上就是寬度為16bit的unicode編碼UTF16,所以,如果我們的 c/c++程序中完全使用wchar類(lèi)型,那么理論上是不需要這種轉(zhuǎn)換的。但是實(shí)際上,我們不可能完全用wchar來(lái)取代char的,所以就目前大多數(shù)應(yīng)用而言,轉(zhuǎn)換仍然是必須的。
具體的轉(zhuǎn)換函數(shù),linux和win32都有一定的支持,比如glibc的mbstowcs就可以用來(lái)把 GB2312編碼轉(zhuǎn)成UTF16,但是這種支持一般是平臺(tái)相關(guān)的(因?yàn)閏/c++的標(biāo)準(zhǔn)中并沒(méi)有包括這部分),不全面的(比如glibc就沒(méi)有提供轉(zhuǎn)成 UTF8的編碼),不獨(dú)立的(linux下mbstowcs的行為要受到locale設(shè)置的影響)。所以我推薦使用iconv庫(kù)來(lái)完成轉(zhuǎn)換。
iconv庫(kù)是一個(gè)免費(fèi)的獨(dú)立的編碼轉(zhuǎn)換庫(kù),支持很多平臺(tái),多種編碼(事實(shí)上,它幾乎可以處理我們所使用的所有字符編碼),而且它的行為不受任何外部環(huán)境的影響。iconv在*nix平臺(tái)上,基本上是缺省安裝的。在win32平臺(tái)上需要額外安裝。
下面提供一個(gè)把GB2312編碼的字符串轉(zhuǎn)換成UTF8編碼的示例
- #include <iconv.h>char* BytesToUtf8(string src, char* dst, int* nout) { size_t n_in = src.length(); size_t n_out = *nout; iconv_t c = iconv_open("UTF-8", "GB2312"); if (c == (iconv_t)-1) { cerr << strerror(errno) << endl; return NULL;}
- char* inbuf = new char [n_in + 1];
- if (!inbuf) {
- iconv_close(c);
- return NULL;
- }
- strcpy(inbuf, src.c_str());
- memset(dst, 0, n_out);
- char* in = inbuf;
- char* out = dst;
- if (iconv(c, &in, &n_in, &out, &n_out) == (size_t)-1) {
- cerr << strerror(errno) << endl;
- out = NULL;
- }
- else {
- n_out = strlen(dst);
- out = dst;
- }
- iconv_close(c);
- *nout = n_out;
- delete[] inbuf;
- return out;
- }
補(bǔ)充幾點(diǎn)說(shuō)明:
1、從jni的接口看,jni提供了UTF16和UTF8兩個(gè)系列的字符串處理函數(shù),但是由于jni的文檔中說(shuō),jni的內(nèi)部實(shí)現(xiàn)中是用UTF8作為字符串編碼格式的,所以使用UTF8系列比較合適(NewStringUTF/GetStringUTFChars /ReleaseStringUTFChars)
2、使用iconv庫(kù)的話,運(yùn)行環(huán)境的設(shè)置對(duì)于編碼轉(zhuǎn)換是沒(méi)有影響的,但是外層java程序?qū)τ谧址慕馕鲆蕾?lài)于運(yùn)行環(huán)境的locale,所以設(shè)置正確的locale對(duì)于jni意義不大,但是對(duì)整個(gè)系統(tǒng)還是必要的。
以上是主要是說(shuō)明使用第三方庫(kù)去解決編碼問(wèn)題,針對(duì)僅在windows平臺(tái)下,是可以使用windows提供的相關(guān)方法進(jìn)行編碼轉(zhuǎn)換的。
使用一下方法可以將jstring轉(zhuǎn)換為char*,主要用于在C++中接收java傳遞過(guò)來(lái)的參數(shù)時(shí)包含中文字符時(shí)使用。在轉(zhuǎn)換過(guò)程中已經(jīng)對(duì)編碼進(jìn)行了轉(zhuǎn)換,可以正常返回出中文字符。
- char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)
- {
- jsize len = pJNIEnv->GetStringLength(jstr);
- const jchar * jcstr = pJNIEnv->GetStringChars(jstr, NULL);
- int size = 0;
- char * str = (char *)malloc(len * 2 + 1);
- if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)
- return NULL;
- pJNIEnv->ReleaseStringChars(jstr, jcstr);
- str[size] = 0;
- return str;
- }
注意:以上方法中返回的char*在使用過(guò)后需要delete釋放,因?yàn)樵诰幋a轉(zhuǎn)換過(guò)程中使用了malloc分配了內(nèi)存,不釋放會(huì)發(fā)生內(nèi)存泄漏。
如果需要在C++中返回中文信息給java,則需要以下方法將char*轉(zhuǎn)換成jstring。
- jstring WindowsTojstring( JNIEnv* env, char* str )
- {
- jstring rtn = 0;
- int slen = strlen(str);
- unsigned short * buffer = 0;
- if( slen == 0 )
- rtn = (env)->NewStringUTF(str );
- else
- {
- int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
- buffer = (unsigned short *)malloc( length*2 + 1 );
- if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
- rtn = (env)->NewString( (jchar*)buffer, length );
- }
- if( buffer )
- free( buffer );
- return rtn;
- }
根據(jù)前面的字符流的分析,和后面提供的轉(zhuǎn)換方法,基本上可以解決jni中中文參數(shù)亂碼的問(wèn)題。
以上內(nèi)容部分摘自網(wǎng)絡(luò)。屬于個(gè)人總結(jié)內(nèi)容。
原文鏈接:http://www.cnblogs.com/bluesky4485/archive/2011/12/13/2285802.html
【編輯推薦】
- 從Java的角度理解Ext的extend
- Windows 64位環(huán)境的Java服務(wù)配置
- 解讀Java環(huán)境變量配置
- 利用JavaMail API 解析MIME
- 詳細(xì)解析Java中抽象類(lèi)和接口的區(qū)別