在上一篇中介绍了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类型,思考下面两个问题:
- Java中
String
使用Unicode编码,C/C++中的char *
是UTF-8编码(Linux/Android/MacOS),如何转换编码? - 所有引用类型都面对一个问题,内存管理方式不同,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会进行内存管理。len
是unicodeChars
的长度。 -
GetStringLength()
,返回string
中Unicode字符的数量。 -
GetStringChars()
,返回string
的表示的Unicode编码的字符数组。在调用ReleaseStringChars()
前都可用。这里就需要自行进行内存管理。如果isCopy
不是空,如果返回的数组是一个拷贝,其值会置为JNI_TRUE
,否则置为JNI_FALSE
。 -
ReleaseStringChars()
,通知JVM,native代码不会再使用chars
,chars
是使用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, 枫竹梦
本文完。