JNI使用时,需要传递中文,遇到乱码问题,多次尝试失败。遂仔细研究了下编码格式。
首先,需要明确几个关于字符编码的基本概念:
◆ java内部是使用的16bit的unicode编码(utf-16)来表示字符串的,无论英文还是中文都是2字节;
◆ jni内部是使用utf-8编码来表示字符串的,utf-8是变长编码的unicode,一般ascii字符是1字节,中文是3字节;
◆ c/c++使用的是原始数据,ascii就是一个字节,中文一般是GB2312编码,用2个字节表示一个汉字。
C语言中的汉字传递到java端
先来实现C语言中的汉字传递到java端,有两种方案:
- 1,在C端以字节传送到java端,在java中转换;
- 2,在C端就转换好。
下面来详细说明:
1,在C端以字节传送到java端,在java中转换编码格式;
在C中,将char*转换为jbyteArray,然后设置数据:
jbyteArray byteArray = env->NewByteArray(strlen(str));
env->SetByteArrayRegion(byteArray,0,strlen(str),(jbyte *)str);
在Java端,获取数组,然后从”gb2312”格式生成utf16格式的java字符串:
byte[] byteArray = JniClient.getChineseByteArray();
strSayHello = new String(byteArray,"gb2312");
2,在C端就转换好编码格式
步骤要复杂一些,核心还是一样的,是在C端通过jni访问java的String创建方法,进行编码格式转换。
jstring CStr2Jstring(JNIEnv* env, char* buf)
{
jclass Class_string;
jmethodID mid_String, mid_getBytes;
jbyteArray bytes;
jbyte* log_utf8;
jstring codetype, jstr;
Class_string = env->FindClass( "java/lang/String"); //获取class
//先将gbk字符串转为java里的string格式
mid_String = env->GetMethodID( Class_string, "<init>",
"([BLjava/lang/String;)V");
int len=strlen(buf)+1;//需要加1,把字符串的结束符也包含进来
bytes = env->NewByteArray( len);
env->SetByteArrayRegion( bytes, 0, len, (jbyte*) buf);
codetype = env->NewStringUTF( "gbk");
jstr = (jstring)env->NewObject( Class_string, mid_String, bytes, codetype);
env->DeleteLocalRef( bytes);
//再将string变utf-8字符串。
mid_getBytes = env->GetMethodID( Class_string, "getBytes", "(Ljava/lang/String;)[B");
codetype = env->NewStringUTF( "utf-8");
bytes = (jbyteArray)env->CallObjectMethod( jstr, mid_getBytes, codetype);
log_utf8 = env->GetByteArrayElements( bytes, JNI_FALSE);
return env->NewStringUTF((char *)log_utf8);
}
java中的汉字传递到C端
下面来实现java中的汉字传递到C端,也是两种方案:
- 1,在Java端就转换好编码格式。
- 2,java端只传递,在C端转换;
下面来详细说明:
1,在Java端就转换好编码格式
在Java中,将String转换为”gbk”格式的byte[]:
String strToJNI = "欢迎";
byte[] byteArr = new byte[strToJNI.length()*2];
byteArr=strToJNI.getBytes("gbk");
在C中,获取jbyteArray的起始地址,当成char*的地址访问即可:
jbyte * byteArr=env->GetByteArrayElements(byteArray,0);
char * buf = (char *)byteArr;
2,java端只传递,在C端转换;
在C语言中做编码格式的转换,步骤要复杂一些,但是核心思想还是一样的,就是在C端通过jni访问java的String创建方法,进行编码格式转换。
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");//寻找 java里面String.class
jstring strencode = env->NewStringUTF("GB2312");//创建java字符串 "gb2312"
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");//寻找到java String getbytes();
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr,mid,strencode); // String .getByte("GB2312");
jsize alen = env->GetArrayLength(barr); //获取长度
jbyte* ba = env->GetByteArrayElements(barr,JNI_FALSE);
//jbyteArray转为jbyte*
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
env->ReleaseByteArrayElements(barr,ba,0); //释放掉
return rtn;
}
总结
1,优先选择在java端做字符编码格式的转换
通过上面的介绍,我们可以看出,在Java端实现编码格式的转换,代码更简洁,也更易于理解。所以,我们应该优先选择在java端做字符编码格式的转换。
2,C中字符串的长度
在C中向外传递时需要加1,将结束符传递出来(见CStr2Jstring())。否则,后面不知道会跟着什么随机数据的。
在java向C传递的byte数组,逐字节打印数组内容,数组的长度不能使用strlen(buf)(见SetChineseByteArray())。
传递的是byte数组,而不是字符串,所以这个长度不能使用strlen,而是需要调用env->GetArrayLength(byteArray)来获取。
3,C中的char类型
在C语言中调用日志打印函数,中文打印为乱码。
我是通过打印具体字节数据来验证传递成功的。
打印语句如下:
LOGI( "log: buf[%d]=%d",i,*(signed char *)(buf+i));
为何要强制转换为 signed char 呢?
我之前未使用强制转换时,打印出来的数据,并不一致,如下:
06-26 16:35:50.220: I/Hello(4529): log: byteArr[0]=-60
06-26 16:35:50.220: I/logfromc(4529): log: buf[0]=196
这两个数据有差异的原因在于:java中的byte,取值范围为[-128,127]。
在ARM平台上,C语言中的char的取值范围[0,255],也就是说,arm上的char是无符号的。通过计算:-60=196-256,可以知道值是匹配的。但是这样靠人脑计算比较累,所以就让电脑干啦(强制转换成 signed char)。
通常我们的手机的cpu都是ARM,例如我的手机就是arm啦,所以用char类型打印出来的值是不同的。
在模拟器上测试,C语言中的char是有符号的,取值范围[-128,127],因为模拟器是在PC上跑的,PC是x86平台。
char是有点特殊的啦,在不同平台上编译出来的情况,有所不同,可要注意哦。