在上一篇中介绍了JNI的基本数据类型。本文是JNI系列的第三篇,介绍JNI中的除了基本类型之外的引用类型—String类型。

系列文章的大纲如下:

  • JNI 简介
  • JNI 基本类型
  • JNI String
  • JNI 数组
  • JNI 实例变量
  • JNI 静态变量
  • JNI 回调实例方法与静态方法
  • JNI 调用Java中的super.method()
  • JNI 中创建对象
  • JNI 中创建对象数组
  • JNI 中局部引用和全局引用
  • JNI 动态注册
  • 使用Android NDK编译Android的Native库

JNI引用类型

在JNI定义了一系列的引用类型,与Java中的类型对应,有如下的继承关系:

// JNI 类型                   Java类型
jobject                      (all Java objects)
|-- jclass                   (java.lang.Class objects)
|-- jstring                  (java.lang.String objects
|-- jarray                   (arrays)
|   |-- jobjectArray         (object arrays)
|   |-- jbooleanArray        (boolean arrays)
|   |-- jbyteArray           (byte arrays)
|   |-- jcharArray           (char arrays)
|   |-- jshortArray          (short arrays)
|   |-- jintArray            (int arrays)
|   |-- jlongArray           (long arrays)
|   |-- jfloatArray          (float arrays)
|   |-- jdoubleArray         (double arrays)
|-- jthrowable               (java.lang.Throwable objects)

在C的实现中,所以的JNI引用类型都是jobject,如:

struct _jobject;

typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

而在C++的实现中,有类似如下的定义:

class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};

typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;

可以看到JNI中所以的引用类型都是指针。

还有jvalue的定义,用于数组元素类型的定义:

typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;

String的常用操作

对String类型,思考下面两个问题:

  1. Java中String使用Unicode编码,C/C++中的char *是UTF-8编码(Linux/Android/MacOS),如何转换编码?
  2. 所有引用类型都面对一个问题,内存管理方式不同,Java中是有GC的,C/C++中需要手动管理内存,JNI中怎么处理?

对于两个问题,JNI提供相应的函数供我们使用,我们使用时需要特别的小心。

JNI的字符操作方法

Unicode编码字符串

jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);

jsize GetStringLength(JNIEnv *env, jstring string);

const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);

void ReleaseStringChars(JNIEnv *env, jstring string,
const jchar *chars);

所有的函数的第一个参数都是JNIEnv *,称为JNI接口指针,在生成的native方法的中第一个参数都是它。

  • NewString(),通过Unicode字符数组构造一个jstring,也就是java.lang.String对象。JVM会进行内存管理。lenunicodeChars的长度。
  • GetStringLength(),返回string中Unicode字符的数量。
  • GetStringChars(),返回string的表示的Unicode编码的字符数组。在调用ReleaseStringChars()前都可用。这里就需要自行进行内存管理。如果isCopy不是空,如果返回的数组是一个拷贝,其值会置为JNI_TRUE,否则置为JNI_FALSE
  • ReleaseStringChars(),通知JVM,native代码不会再使用charschars是使用GetStringChars()得到的返回值。

UTF-8编码字符串

UTF-8编码的字符串与Unicode编码字符串接口类似。平时使用最多的也是下面的几个接口。

jstring NewStringUTF(JNIEnv *env, const char *bytes);

jsize GetStringUTFLength(JNIEnv *env, jstring string);

const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);

void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);

通过下面的实例来加深一下理解。

实例

我们来看一个实例。

还是Hello.java

package myjni;

public class Hello {
    static {
        System.loadLibrary("hello");
    }

    // 返回字符串:"Hey, name"
    public native String hello(String name);
    
    public static void main(String[] args) {
        Hello hello = new Hello();
        
        String name = "furzoom";
        System.out.println("Say hello to " + name + ": " + hello.hello(name));
        
        name = "枫竹梦";
        System.out.println("Say hello to " + name + ": " + hello.hello(name));
    }
}

如果还不清楚如果生成头文件请参考JNI简介

生成头文件myjni_Hello.h的签名为:

/*
 * Class:     myjni_Hello
 * Method:    hello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_myjni_Hello_hello
  (JNIEnv *, jobject, jstring);

实现函数Java_myjni_Hello_hello

JNIEXPORT jstring JNICALL Java_myjni_Hello_hello(JNIEnv *env, jobject obj, jstring jname) {
  // 得到传入的UTF-8编码的字符串
  const char* name = env->GetStringUTFChars(jname, nullptr);
  if (name == nullptr) {
    return nullptr;
  }

  // 构造需要返回的结果字符串
  std::string result("Hey, ");
  result += name;
  // 使用完从GetStringUTFChars的字符串后,需要通知JVM
  env->ReleaseStringUTFChars(jname, name);

  // 从JNI的UTF-8编码字符串构造jstring
  return env->NewStringUTF(result.c_str());
}

编译生成动态库,并运行Java程序得到输出:

Say hello to furzoom: Hey, furzoom
Say hello to 枫竹梦: Hey, 枫竹梦


本文完。