(三)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 数组进行传递字符串,这种方式更简单。

猜你喜欢

转载自blog.csdn.net/qq_18983205/article/details/78840507