(三)JNI 中文乱码

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、分析

在 JNI 中使用中文出现乱码的主要原因是 java、JNI 和 C、C++ 使用的编码不同。

  1. java 内部是使用的 16 bit 的 unicode 编码(utf-16)来表示字符串的,无论英文还是中文都是 2 字节。
  2. JNI 内部是使用 utf-8 编码来表示字符串的,utf-8 是变长编码的 unicode,一般 ascii 字符是 1 字节,中文是 3 字节。
  3. C/C++ 使用的是原始数据,ascii 就是一个字节,中文一般是 GB2312 编码,用 2 个字节表示一个汉字。

二、java --> C

1.C 端进行编码转换

java 调用的时候使用的是 utf-16 编码的字符串,jvm 把这个参数传递给 JNI,C 得到的输入是 jstring,此时,可以利用 JNI 提供的两种函数进行解析

一个是 GetStringUTFChars,这个函数将得到一个 UTF-8 编码的字符串;另一个是 GetStringChars 这个将得到 UTF-16 编码的字符串。无论那个函数,得到的字符串如果含有中文,都需要进一步转化成 GB2312 的编码。

java:

public class JNIString {

	public native String getString(String string);
	
	static{
		System.loadLibrary("FirstApplication");
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException {
		
		// C 进行解析
		JNIString jniMain = new JNIString();
		String string = jniMain.getString("java 字符串");	
	}
}

C:

JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIString_getString
(JNIEnv *env, jobject jobj, jstring string) {
	//C 解析 java 字符串
	int length = (*env)->GetStringLength(env, string);
	const jchar * jcstr = (*env)->GetStringChars(env, string, NULL);

	if (jcstr == NULL) {
		return NULL;
	}
	//jchar -> char
	char * rtn = (char *)malloc(sizeof(char) * (2 * length + 1));
	memset(rtn, 0, sizeof(char) * (2 * length + 1));
	int size = 0;
	size = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)jcstr, length, rtn, sizeof(char) * (2 * length + 1), NULL, NULL);

	printf("string: %s\n", rtn);
	return NULL;
}

这边采用的 window 环境提供的方法 WideCharToMultiByte 进行解析,在 C 端解析较麻烦,这边不分析。

2.java 端进行编码转换

在 java中,将 String 转换为 "GB2312”格式的 byte 数组。在 C 中,获取 jbyteArray 的起始地址,当成 char* 的地址访问即可。

java:

public class JNIString {

	public native byte[] getString2(byte[] byteArr);
	
	static{
		System.loadLibrary("FirstApplication");
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException {

		JNIString jniMain = new JNIString();
	
		//java 进行解析
		String strToJNI = "java 字符串";
		byte[] byteArry = strToJNI.getBytes("GB2312");
		byte[] byteArry2 = jniMain.getString2(byteArry);	
	}
}

C:

JNIEXPORT jbyteArray JNICALL Java_com_xiaoyue_JNIString_getString2
(JNIEnv *env, jobject jobj, jbyteArray byteArray) {

	//C 解析 java 字符串
	jbyte * byteArr = (*env)->GetByteArrayElements(env, byteArray, 0);
	jsize size = (*env)->GetArrayLength(env, byteArray);// 获取数组长度

	char* str = NULL;
	if (size > 0) {
		str = (char*)malloc(size + 1);// "\0"
		memcpy(str, byteArr, size);
		str[size] = 0;
	}

	(*env)->ReleaseByteArrayElements(env, byteArray, byteArr, 0);
	printf("string: %s\n", str);

	return NULL;
}

三、C --> java

1.C 端进行编码转换

java:

public class JNIString {

	public native String getString(String string);
	
	static{
		System.loadLibrary("FirstApplication");
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException {
		// C 进行解析
		JNIString jniMain = new JNIString();
		String string = jniMain.getString("java 字符串");

		System.out.println(string);
	}
}

C:

JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIString_getString
(JNIEnv *env, jobject jobj, jstring string) {

	//C 返回 java 字符串
	char *c_str = "C 字符串";
	jclass str_cls = (*env)->FindClass(env, "java/lang/String");
	jmethodID jmid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");
	
	//jstring -> jbyteArray
	jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
	// 将Char * 赋值到 bytes
	(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
	jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

	return (*env)->NewObject(env, str_cls, jmid, bytes, charsetName);

}

C 端进行中文字符转换传递个 java 的时候,也是调用了 java 中 String 类的编码转换方式进行转换。

2.java 端进行编码转换

在 C 中,将 char* 转换为 jbyteArray,然后返回。在 Java端,获取数组,然后从"GB2312”格式生成 utf16 格式的java字符串.
java:

public class JNIString {
	public native byte[] getString2(byte[] byteArr);
	
	static{
		System.loadLibrary("FirstApplication");
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException {
		// TODO Auto-generated method stub

		JNIString jniMain = new JNIString();

		//java 进行解析
		String strToJNI = "java 字符串";
		byte[] byteArry = strToJNI.getBytes("GB2312");
		byte[] byteArry2 = jniMain.getString2(byteArry);
		String string2 = new String(byteArry2, "GB2312");
		System.out.println(string2);
		
	}
	
}

C:

JNIEXPORT jbyteArray JNICALL Java_com_xiaoyue_JNIString_getString2
(JNIEnv *env, jobject jobj, jbyteArray byteArray) {
	//C 返回 java 字符串
	char *c_str = "C 字符串";
	jbyteArray byteArray1 = (*env)->NewByteArray(env, strlen(c_str));
	(*env)->SetByteArrayRegion(env, byteArray1, 0, strlen(c_str), (jbyte *)c_str);
	return byteArray1;
}

四、代码

完整代码:
java:

public class JNIString {

	public native String getString(String string);
	public native byte[] getString2(byte[] byteArr);
	
	static{
		System.loadLibrary("FirstApplication");
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException {
		// TODO Auto-generated method stub

		// C 进行解析
		JNIString jniMain = new JNIString();
		String string = jniMain.getString("java 字符串");

		System.out.println(string);
		
		//java 进行解析
		String strToJNI = "java 字符串";
		byte[] byteArry = strToJNI.getBytes("GB2312");
		byte[] byteArry2 = jniMain.getString2(byteArry);
		String string2 = new String(byteArry2, "GB2312");
		System.out.println(string2);
		
	}
	
}

C:

#include "stdafx.h"

#include "com_xiaoyue_JNIString.h"
#include <string.h>
#include <Windows.h>

JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIString_getString
(JNIEnv *env, jobject jobj, jstring string) {
	//C 解析 java 字符串
	int length = (*env)->GetStringLength(env, string);
	const jchar * jcstr = (*env)->GetStringChars(env, string, NULL);

	if (jcstr == NULL) {
		return NULL;
	}
	//jchar -> char
	char * rtn = (char *)malloc(sizeof(char) * (2 * length + 1));
	memset(rtn, 0, sizeof(char) * (2 * length + 1));
	int size = 0;
	size = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)jcstr, length, rtn, sizeof(char) * (2 * length + 1), NULL, NULL);

	printf("string: %s\n", rtn);

	//C 返回 java 字符串
	char *c_str = "C 字符串";
	jclass str_cls = (*env)->FindClass(env, "java/lang/String");
	jmethodID jmid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");
	
	//jstring -> jbyteArray
	jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
	// 将Char * 赋值到 bytes
	(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
	jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

	return (*env)->NewObject(env, str_cls, jmid, bytes, charsetName);

}

JNIEXPORT jbyteArray JNICALL Java_com_xiaoyue_JNIString_getString2
(JNIEnv *env, jobject jobj, jbyteArray byteArray) {

	//C 解析 java 字符串
	jbyte * byteArr = (*env)->GetByteArrayElements(env, byteArray, 0);
	jsize size = (*env)->GetArrayLength(env, byteArray);// 获取数组长度

	char* str = NULL;
	if (size > 0) {
		str = (char*)malloc(size + 1);// "\0"
		memcpy(str, byteArr, size);
		str[size] = 0;
	}

	(*env)->ReleaseByteArrayElements(env, byteArray, byteArr, 0);
	printf("string: %s\n", str);

	//C 返回 java 字符串
	char *c_str = "C 字符串";
	jbyteArray byteArray1 = (*env)->NewByteArray(env, strlen(c_str));
	(*env)->SetByteArrayRegion(env, byteArray1, 0, strlen(c_str), (jbyte *)c_str);
	return byteArray1;
}

五、总结

通过上面比较,明显可以看出 JNI 传递中文时,在 java 端进行编码转换,通过 byte 数组进行传递字符串,这种方式更简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值