1. 程式人生 > >jni和C++通訊中文亂碼的問題

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

轉:https://blog.csdn.net/Sasoritattoo/article/details/18010623

 

專案中有些東西要放到jni層,傳入字元,還要傳出字元,其中字元有中文,傳入底層的時候是沒問題的,但是從底層傳出就有問題了,使用的方法是GetStringUTFChars/NewStringUTF,很奇怪,網上搜到這篇文章,講的很清晰透徹,一切都明白了。

所以,總結一下:傳入jni層引數可以是jstring型別,這樣寫比jbyteArray簡便些,但是如果函式需要返回字串並且有可能是中文的話,最簡便的方法還是返回jbyteArray型別,然後android上面再轉成string型別,不然的話,有可能會出現亂碼。

 

以下為轉載文章:

 

轉自: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的編碼。

       String 
      (UTF-16)
          |
[java]    |
--------------------  JNI 呼叫
[cpp]     |
          v
       jstring 
       (UTF-16)
          |   
 +--------+---------+
 |GetStringChars    |GetStringUTFChars
 |                  |
 v                  v
wchar_t*           char*
(UTF_16)           (UTF-8)
  • c/c++ –> java

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

       String 
      (UTF-16)
          ^
          |
[java]    |
--------------------  JNI 返回
[cpp]     |
       jstring 
       (UTF-16)
          ^
          |   
 +--------+---------+
 ^                  ^
 |                  |
 |NewString         |NewStringUTF
wchar_t*          char*
(UTF_16)          (UTF-8)

 

如果字串中不含中文字元,只是標準的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編碼的示例

#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;
}

 

補充幾點說明:

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中中文引數亂碼的問題。

以上內容部分摘自網路。屬於個人總結內容。