JNI和C++通信中文乱码的问题

开发 后端
从Java1.1开始,JNI标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

首先,需要明确几个关于编码的基本概念:

◆  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的编码。

  1.        String  
  2.       (UTF-16)  
  3.           |  
  4. [java]    |  
  5. --------------------  JNI 调用  
  6. [cpp]     |  
  7.           v  
  8.        jstring  
  9.        (UTF-16)  
  10.           |     
  11.  +--------+---------+   
  12.  |GetStringChars    |GetStringUTFChars  
  13.  |                  |  
  14.  v                  v  
  15. wchar_t*           char*  
  16. (UTF_16)           (UTF-8) 

◆  c/c++ –> java

jni返回给java的字符串,c/c++首先应该负责把这个字符串变成UTF-8或者UTF-16格式,然后通过NewStringUTF或者NewString来把它封装成jstring,返回给java就可以了。

  1.        String  
  2.       (UTF-16)   
  3.           ^   
  4.           |  
  5. [java]    |  
  6. --------------------  JNI 返回  
  7. [cpp]     |       jstring  
  8.        (UTF-16)  
  9.           ^  
  10.           |  
  11.  +--------+---------+  
  12.  ^                  ^  
  13.  |                  |  
  14.  |NewString         |NewStringUTF  
  15. wchar_t*          char*  
  16. (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编码的示例

  1. #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;}   
  2.    
  3.    
  4. char* inbuf = new char [n_in + 1];  
  5.  if (!inbuf) {  
  6.  iconv_close(c);  
  7.  return NULL;  
  8.  }  
  9.    
  10. strcpy(inbuf, src.c_str());  
  11.  memset(dst, 0, n_out);  
  12.    
  13. char* in = inbuf;  
  14.  char* out = dst;  
  15.  if (iconv(c, &in, &n_in, &out, &n_out) == (size_t)-1) {  
  16.  cerr << strerror(errno) << endl;  
  17.  out = NULL;  
  18.  }  
  19.  else {  
  20.  n_out = strlen(dst);  
  21.  out = dst;  
  22.  }  
  23.    
  24. iconv_close(c);  
  25.  *nout = n_out;  
  26.  delete[] inbuf;  
  27.    
  28. 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传递过来的参数时包含中文字符时使用。在转换过程中已经对编码进行了转换,可以正常返回出中文字符。

  1. char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)  
  2. {  
  3.     jsize len = pJNIEnv->GetStringLength(jstr);  
  4.     const jchar * jcstr = pJNIEnv->GetStringChars(jstr, NULL);  
  5.     int size = 0;  
  6.     char * str = (char *)malloc(len * 2 + 1);  
  7.     if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)  
  8.         return NULL;  
  9.     pJNIEnv->ReleaseStringChars(jstr, jcstr);  
  10.     str[size] = 0;  
  11.     return str;  

注意:以上方法中返回的char*在使用过后需要delete释放,因为在编码转换过程中使用了malloc分配了内存,不释放会发生内存泄漏。

如果需要在C++中返回中文信息给java,则需要以下方法将char*转换成jstring。

  1. jstring WindowsTojstring( JNIEnv* env, char* str )  
  2. {  
  3.     jstring rtn = 0;  
  4.     int slen = strlen(str);  
  5.     unsigned short * buffer = 0;  
  6.     if( slen == 0 )  
  7.         rtn = (env)->NewStringUTF(str );   
  8.     else 
  9.     {  
  10.         int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );  
  11.         buffer = (unsigned short *)malloc( length*2 + 1 );  
  12.         if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )  
  13.             rtn = (env)->NewString(  (jchar*)buffer, length );  
  14.     }  
  15.     if( buffer )  
  16.         free( buffer );  
  17.     return rtn;  

根据前面的字符流的分析,和后面提供的转换方法,基本上可以解决jni中中文参数乱码的问题。

以上内容部分摘自网络。属于个人总结内容。

原文链接:http://www.cnblogs.com/bluesky4485/archive/2011/12/13/2285802.html

【编辑推荐】

  1. 从Java的角度理解Ext的extend
  2. Windows 64位环境的Java服务配置
  3. 解读Java环境变量配置
  4. 利用JavaMail API 解析MIME
  5. 详细解析Java中抽象类和接口的区别

 

责任编辑:林师授 来源: bluesky的博客
相关推荐

2010-06-07 11:13:46

MySQL中文乱码

2010-06-09 14:54:29

2010-06-02 16:36:38

连接MySQL中文乱码

2009-06-30 13:49:21

excel文档Jsp

2018-12-07 08:38:49

代理服务器Web服务器通信

2014-09-19 10:46:36

LuaCC++

2010-11-09 17:09:23

SQL Server中

2009-11-26 16:30:52

Suse中文乱码问题

2011-02-24 10:48:51

FireFTP

2010-06-03 10:26:29

开发MySQL中文乱码

2022-12-06 13:02:27

深度学习AI

2024-03-11 06:05:00

C++字符串

2012-05-09 09:56:04

WP7乱码

2011-08-08 10:29:12

MySQL

2021-09-23 14:41:58

鸿蒙HarmonyOS应用

2010-03-18 17:18:12

Python 中文乱码

2010-09-07 16:34:31

统一通信sip

2011-06-09 14:13:06

C++JAVA缺省初始化

2009-07-02 13:26:32

JSP中文乱码

2009-07-01 18:05:54

JSP中文乱码
点赞
收藏

51CTO技术栈公众号