天天看點

jni和C++通信中文亂碼的問題

http://www.cnblogs.com/bluesky4485/archive/2011/12/13/2285802.html

首先,需要明确幾個關于編碼的基本概念:

java内部是使用的16bit的unicode編碼(utf-16)來表示字元串的,無論英文還是中文都是2位元組;

jni内部是使用utf-8編碼來表示字元串的,utf-8是變長編碼的unicode,一般ascii字元是1位元組,中文是3位元組;

c/c++使用的是原始資料,ascii就是一個位元組,中文一般是gb2312編碼,用2個位元組表示一個漢字。

jni的中文字元串處理

先從字元流的方向分别對java-->c++和c++-->java進行分析

java-->c++

這種情況下,java調用的時候使用的是utf-16編碼的字元串,jvm把這個參數傳遞給jni,c++得到的輸入是jstring,此時,可以利用jni提供的兩種函數,一個是getstringutfchars,這個函數将得到一個utf-8編碼的字元串;另一個是getstringchars這個将得到utf-16編碼的字元串。無論那個函數,得到的字元串如果含有中文,都需要進一步轉化成gb2312的編碼。

c/c++ –> java

jni傳回給java的字元串,c/c++首先應該負責把這個字元串變成utf-8或者utf-16格式,然後通過newstringutf或者newstring來把它封裝成jstring,傳回給java就可以了。

如果字元串中不含中文字元,隻是标準的ascii碼,那麼用getstringutfchars/newstringutf就可以搞定了,因為這種情況下,utf-8編碼和ascii編碼是一緻的,不需要轉換。

但是如果字元串中有中文字元,那麼在c/c++部分進行編碼轉換就是一個必須了。我們需要兩個轉換函數,一個是把utf8/16的編碼轉成gb2312;一個是把gb2312轉成utf8/16。

這裡要說明一下:linux和win32都支援wchar,這個事實上就是寬度為16bit的unicode編碼utf16,是以,如果我們的 c/c++程式中完全使用wchar類型,那麼理論上是不需要這種轉換的。但是實際上,我們不可能完全用wchar來取代char的,是以就目前大多數應用而言,轉換仍然是必須的。

具體的轉換函數,linux和win32都有一定的支援,比如glibc的mbstowcs就可以用來把 gb2312編碼轉成utf16,但是這種支援一般是平台相關的(因為c/c++的标準中并沒有包括這部分),不全面的(比如glibc就沒有提供轉成 utf8的編碼),不獨立的(linux下mbstowcs的行為要受到locale設定的影響)。是以我推薦使用iconv庫來完成轉換。

iconv庫是一個免費的獨立的編碼轉換庫,支援很多平台,多種編碼(事實上,它幾乎可以處理我們所使用的所有字元編碼),而且它的行為不受任何外部環境的影響。iconv在*nix平台上,基本上是預設安裝的。在win32平台上需要額外安裝。

下面提供一個把gb2312編碼的字元串轉換成utf8編碼的示例

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;

*nout = n_out;

delete[] inbuf;

return out;

補充幾點說明:

1、從jni的接口看,jni提供了utf16和utf8兩個系列的字元串處理函數,但是由于jni的文檔中說,jni的内部實作中是用utf8作為字元串編碼格式的,是以使用utf8系列比較合适(newstringutf/getstringutfchars /releasestringutfchars)

2、使用iconv庫的話,運作環境的設定對于編碼轉換是沒有影響的,但是外層java程式對于字元串的解析依賴于運作環境的locale,是以設定正确的locale對于jni意義不大,但是對整個系統還是必要的。

以上是主要是說明使用第三方庫去解決編碼問題,針對僅在windows平台下,是可以使用windows提供的相關方法進行編碼轉換的。

使用一下方法可以将jstring轉換為char*,主要用于在c++中接收java傳遞過來的參數時包含中文字元時使用。在轉換過程中已經對編碼進行了轉換,可以正常傳回出中文字元。

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*在使用過後需要delete釋放,因為在編碼轉換過程中使用了malloc配置設定了記憶體,不釋放會發生記憶體洩漏。

如果需要在c++中傳回中文資訊給java,則需要以下方法将char*轉換成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;

根據前面的字元流的分析,和後面提供的轉換方法,基本上可以解決jni中中文參數亂碼的問題。