【JNI】JNI基础语法

1 C 和 C++ 在 JNI 中的区别

        JNI环境搭建 中介绍了在命令行和 Android Studio 中如何编译 JNI 代码,本文将介绍 JNI 的基础语法,主要介绍 JNI 的数据类型、JNI 与 Java 交互、异常处理,参考了 JNI 官方文档,源码详见以下文件。

JDK\include\jni.h
SDK\ndk\27.2.12479018\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\jni.h

        为了避免每个知识点都要介绍一遍 C 和 C++ 的在 JNI 中用法,本文先介绍下 C 和 C++ 在 JNI 中的区别,然后每个知识点只介绍 C++ 在 JNI 中的用法

        以下分别用 C 和 C++ 写了一个 JNI 接口的案例。

        demo.c

#include <jni.h>
#include <string.h>
 
JNIEXPORT jstring JNICALL
Java_com_zhyan8_test_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    return (*env)->NewStringUTF(env, "Hello from C");
}

        demo.cpp

#include <jni.h>
#include <string>
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_zhyan8_test_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    return env->NewStringUTF("Hello from C++");
}

        可以看到,C 与 C++ 的主要区别如下。

  • 头文件:C 中 string 的引用是 #include <string.h>,C++ 中 string 的引用是 #include <string>。
  • extern "C":C++ 中多了 extern "C" 修饰,表示使用 C 风格的函数链接,禁止 C++ 的名称修饰。如果不禁用名称修饰,C++ 编译器会对函数名进行修饰,以支持函数重载等特性,会导致 Java 无法查找到本地函数。因此,使用 extern "C" 修饰的函数不能重载(因为名称不再修饰)。
  • env:C 中的 env 是二级指针,所以访问函数通过 (*env)-> 访问;C++ 中的 env 是一级指针,所以访问函数通过 env-> 访问。

2 Native 方法注册

        在 Java 中调用 Native 方法时,JVM 是如何找到对应的 JNI 方法的?这里就涉及到 Native 方法注册问题,JNI 提供了两种将 Native 方法与 Java 代码绑定的方式:静态注册和动态注册。它们的对比如下。官方介绍见 → registering-native-methods

特性静态注册动态注册
关联方式通过固定命名规则自动关联手动注册方法映射表
方法名必须包含完整包名类名可自定义
效率较低 (需要按名称查找)较高 (直接映射)
灵活性
实现复杂度简单较复杂
适用场景简单项目、少量 native 方法复杂项目、大量 native 方法

2.1 静态注册

        采用静态注册时,本地方法名必须遵循 Java_包名_类名_方法名 的格式。如果 Java 类的全路径中包含下划线,对应的 JNI 方法命名中使用 "_1" 替换下划线,如下。官方介绍见 → resolving-native-method-names

// java类全路径
com.zhhyan8_a.Demo#nativeTest
// 对应的JNI接口命名
Java_com_zhyan8_1a_Demo_nativeTest

2.2 动态注册

        在 Java 中调用 System.loadLibrary("test") 时,会自动调用 JNI_OnLoad 方法,在该方法中调用 RegisterNatives 方法即可注册 Native 方法;当包含本地库的类加载器(ClassLoader)被垃圾回收或 JVM 关闭时,会自动调用 JNI_OnUnload 方法,在该方法里可以释放全局引用、关闭打开的文件/网络连接、释放 Native 分配的内存等操作。

static void nativeTest1(JNIEnv* env, jobject thiz)
{
    // native方法实现
}

static jstring nativeTest2(JNIEnv* env, jobject thiz, jint i)
{
    // native方法实现
}

static JNINativeMethod methods[] = {
        { "nativeTest1", "()V", (void*) nativeTest1 },
        { "nativeTest2", "(I)Ljava/lang/String;", (void*) nativeTest2 }
};

// 注册动态方法
static int registerNativeMethods(JNIEnv* env) {
    int result = -1;
    jclass clazz = env->FindClass("com/zhyan8/demo/Test");
    if (clazz != NULL) {
        jint len = sizeof(methods) / sizeof(methods[0]);
        if (env->RegisterNatives(clazz, methods, len) == JNI_OK) {
            result = 0;
        }
    }
    return result;
}

// System.loadLibrary("test") 时自动调用
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_OK) {
        if (NULL != env && registerNativeMethods(env) == 0) {
            result = JNI_VERSION_1_6;
        }
    }
    return result;
}

// 当包含本地库的类加载器(ClassLoader)被垃圾回收、JVM关闭时自动调用
void JNI_OnUnload(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_OK) {
        return;
    }
	// 释放全局引用、关闭打开的文件/网络连接、释放native分配的内存
    jclass clazz = env->FindClass("com/zhyan8/demo/Test");
    if (clazz != NULL) {
        (*env)->UnregisterNatives(env, clazz);
    }
}

        注意:JVM 不保证一定会调用 JNI_OnUnload,特别是在 JVM 异常终止时;JNI_OnUnload 会在JVM 的 finalizer 线程中调用,需要注意线程安全问题;必须通过 JavaVM 指针获取 JNIEnv,不能缓存之前获取的 JNIEnv。 

3 数据类型

        JNI 数据类型官方介绍详见 → types.html

3.1 基本数据类型

        JNI 提供以下 9 种基本数据类型。官方介绍详见 → primitive-types

        可以用这些变量接收 Java 传到 JNI 中的相关变量,如 Java 中传递一个 int 变量到 JNI 中,JNI 函数的入参可以使用 jint 变量接收。另外,这些变量又与 C++ 的变量有对应关系,如下。

typedef unsigned char   jboolean;
typedef signed char     jbyte; // 或 typedef uint8_t jbyte
typedef unsigned short  jchar;
typedef short           jshort;
typedef int32_t         jint;
typedef int64_t         jlong;
typedef float           jfloat;
typedef double          jdouble;

        注意:Java 中 int 和 long 的位数分别是 32 位 和 64 位;C++ 中 int 和 long 的位数依赖于操作系统,int 可能是 16 位 或 32 位,long 可能是 32 位或 64 位,long long 一般是 64 位,而 int32_t 和 int64_t 是固定的 32 位和 64 位,所以推荐使用 int32_t 转换 jint、int64_t 转换 jlong。

        另外提供了 2 个宏常量。

#define JNI_FALSE  0
#define JNI_TRUE   1

        应用如下。 

jboolean b1 = 0;
jboolean b2 = true;
jbyte b = 10;
jchar c = 'A';
jshort s = 20;
jint i = 30;
jlong l = 40; // 或40l、40L
jfloat f = 3.14; // 或3.14f、3.14F
jdouble d = 2.71; // 或2.71d、2.71D

3.2 引用类型

3.2.1 JNI 内置引用类型

        JNI 提供了以下引用类型,官方介绍详见 → reference-types。右边括号中的类是其对应的 Java 类,如:Java 中传递 int[] 数组到 JNI 中,JNI 函数的入参可以使用 jintArray 作为形参接收。从层级结构中可以看到 jobject 是所有 JNI 类的父类。

3.2.2 局部引用、全局引用、弱全局引用 

        引用类型主要分为局部引用全局引用弱全局引用,它们的区别如下。官方介绍详见 → global-and-local-references

  • 局部引用:通过 NewLocalRef 函数或各种 JNI 接口创建(NewStringUTF、NewObject、NewCharArray、FindClass、GetObjectClass 等),会阻止 GC 回收所引用的对象,只在创建它的本地方法调用期间有效,不能跨线程使用,函数返回后局部引用所引用的对象会被 JVM 自动释放,或调用 DeleteLocalRef 函数释放。
  • 全局引用:通过 NewGlobalRef 函数创建,会阻 GC 回收所引用的对象,可以跨方法、跨线程使用,JVM 不会自动释放,必须调用 DeleteGlobalRef 函数手动释放。
  • 弱全局引用:通过 NewWeakGlobalRef 函数创建,不会阻止 GC 回收所引用的对象,可以跨方法、跨线程使用,引用不会自动释放,在 JVM 认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放,或调用 DeleteWeakGlobalRef 函数手动释放,在使用弱全局引用前需要使用 IsSameObject 函数检查对象是否已被回收。
// 局部引用接口
jobject NewLocalRef(jobject ref)
void DeleteLocalRef(jobject obj)
// 全局引用接口
jobject NewGlobalRef(jobject lobj)
void DeleteGlobalRef(jobject gref)
// 弱全局引用接口
jweak NewWeakGlobalRef(jobject obj)
void DeleteWeakGlobalRef(jweak ref)
jboolean IsSameObject(jobject obj1, jobject obj2)

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
   jstring str = env->NewStringUTF("Hello JNI");
   jstring weak_str = (jstring) env->NewWeakGlobalRef(str);
   if (!env->IsSameObject(weak_str, NULL)) {
      const char *c_str = env->GetStringUTFChars(weak_str, NULL);
      printf("%s\n", c_str);
      env->ReleaseStringUTFChars(str, c_str);
   }
   env->DeleteWeakGlobalRef(weak_str);
}

4 字符串

        JNI 中字符串官方介绍详见 → string-operations。JNI 提供了改进的 UTF-8 字符串,改进原理详见 → modified-utf-8-strings

        以下是 JNI 提供的常用字符串接口,jsize 是 jint 的别名。

// 新建字符串
jstring NewString(const jchar *unicode, jsize len)
jstring NewStringUTF(const char *utf)
// 获取字符串长度
jsize GetStringLength(jstring str)
jsize GetStringUTFLength(jstring str)
// 获取字符串首元素指针
const jchar *GetStringChars(jstring str, jboolean *isCopy)
const char* GetStringUTFChars(jstring str, jboolean *isCopy)
// 字符串切片
void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf)
void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf)
// 释放字符串
void ReleaseStringChars(jstring str, const jchar *chars)
void ReleaseStringUTFChars(jstring str, const char* chars)

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
   jstring str = env->NewStringUTF("Hello JNI");

   jsize len1 = env->GetStringLength(str);
   jsize len2 = env->GetStringUTFLength(str);
   printf("%d, %d\n", len1, len2); // 打印: 9, 9

   const char *c_str = env->GetStringUTFChars(str, NULL);
   printf("%s\n", c_str); // 打印: Hello JNI
   env->ReleaseStringUTFChars(str, c_str);

   jchar buffer1[128];
   env->GetStringRegion(str, 2, 6, buffer1);
   buffer1[6] = '\0';
   printf("%ls\n", buffer1); // 打印: llo JN

   char buffer2[128];
   env->GetStringUTFRegion(str, 3, 4, buffer2);
   buffer2[4] = '\0';
   printf("%s\n", buffer2); // 打印: lo J
}

        说明:只有基本数据类型可以直接打印,不能直接打印 jstring,需要将其转换为 char * 或 char[]。

5 数组

        JNI 中数组官方介绍详见 → array-operations。JNI 提供了以下数组类型,右边括号中的类是其对应的 Java 类,如:Java 中传递 int[] 数组到 JNI 中,JNI 函数的入参可以使用 jintArray 作为形参接收。

        以下是 JNI 提供的常用数组接口,jsize 是 jint 的别名。

// 新建数组
jbooleanArray NewBooleanArray(jsize len)
jbyteArray NewByteArray(jsize len)
jcharArray NewCharArray(jsize len)
jshortArray NewShortArray(jsize len)
jintArray NewIntArray(jsize len)
jlongArray NewLongArray(jsize len)
jfloatArray NewFloatArray(jsize len)
jdoubleArray NewDoubleArray(jsize len)
jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init)

// 获取数组元素
jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy)
jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy)
jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy)
jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy)
jint * GetIntArrayElements(jintArray array, jboolean *isCopy)
jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy)
jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy)
jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy)
jobject GetObjectArrayElement(jobjectArray array, jsize index)

// 设置元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject val)

// 获取数组长度
jsize GetArrayLength(jarray array)

// 设置数组切片
void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf)
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf)

// 获取数组切片
void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf)
void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf)
void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf)
void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf)
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf)
void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf)
void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf)
void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf)

// 释放数组元素
void ReleaseBooleanArrayElements(jbooleanArray array, jboolean *elems, jint mode)
void ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode)
void ReleaseCharArrayElements(jcharArray array, jchar *elems, jint mode)
void ReleaseShortArrayElements(jshortArray array, jshort *elems, jint mode)
void ReleaseIntArrayElements(jintArray array, jint *elems, jint mode)
void ReleaseLongArrayElements(jlongArray array, jlong *elems, jint mode)
void ReleaseFloatArrayElements(jfloatArray array, jfloat *elems, jint mode)
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble *elems, jint mode)

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
   jsize size = 5;
   jint temp[] = {5, 3, 7, 2, 6};

   jintArray array = env->NewIntArray(size);
   if (array == NULL) {
       return;  // 内存分配失败
   }
   env->SetIntArrayRegion(array, 0, size, temp);

   jint *elements = env->GetIntArrayElements(array, NULL);
   if (elements != NULL) {
       for (int i = 0; i < size; i++) {
           printf("%d\n", elements[i]); // 打印: 5, 3, 7, 2, 6
       }
       env->ReleaseIntArrayElements(array, elements, 0);
   }
}

        补充:通过以下方式将 jbyte*、jint* 等类型转换为 uint8_t*、uint32_t* 等类型。

jbyte* jByteP = env->GetByteArrayElements(jbyteArrayData, NULL);
uint8_t* cByteP = reinterpret_cast<uint8_t*>(jByteP);

jint* jIntP = env->GetIntArrayElements(jintArrayData, NULL);
uint32_t* cIntP = reinterpret_cast<uint32_t*>(jIntP);

6 类操作

        JNI 中类操作官方介绍详见 → class-operations

        以下是 JNI 提供的常用类操作接口。

// 查找类
jclass FindClass(const char *name)
// 获取父类
jclass GetSuperclass(jclass sub)
// 判断sub是否可以安全地转换为sup
jboolean IsAssignableFrom(jclass sub, jclass sup)

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass dateClass = env->FindClass("java/util/Date");
    jclass superClass = env->GetSuperclass(dateClass);
    jboolean res1 = env->IsAssignableFrom(dateClass, superClass); // true
    jboolean res2 = env->IsAssignableFrom(superClass, dateClass); // false
    printf("%d, %d\n", res1, res2); // 打印: 1, 0
}

7 对象操作

        JNI 中对象操作官方介绍详见 → object-operations

        以下是 JNI 提供的常用对象操作接口。

// 分配对象(仅分配内存, 未调用构造函数)
jobject AllocObject(jclass clazz)
// 创建对象
jobject NewObject(jclass clazz, jmethodID methodID, ...)
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args)
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
// 获取对象所属类
jclass GetObjectClass(jobject obj)
// 获取对象引用类型
jobjectRefType GetObjectRefType(jobject obj)
// 判断对象是否是某个类的实例
jboolean IsInstanceOf(jobject obj, jclass clazz)
// 判断是否是同一个对象
jboolean IsSameObject(jobject obj1, jobject obj2)

        jvalue 是联合体类型,所有元素共享一个空间,主要用于 A 方法的入参(以 A 结尾的方法,如 NewObjectA)。官方介绍详见 → the-value-type

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

        jobjectRefType 是枚举类型。

typedef enum _jobjectType {
     JNIInvalidRefType    = 0, // 无效的引用
     JNILocalRefType      = 1, // 局部引用
     JNIGlobalRefType     = 2, // 全局引用
     JNIWeakGlobalRefType = 3  // 弱全局引用
} jobjectRefType;

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass dateClass = env->FindClass("java/util/Date");
    jmethodID constructor = env->GetMethodID(dateClass, "<init>", "()V");
    jobject dateObj = env->NewObject(dateClass, constructor);

    jclass clazz = env->GetObjectClass(dateObj);
    jboolean res1 = env->IsInstanceOf(dateObj, clazz); // true
    printf("%d\n", res1); // 打印: 1

    jobject globalObj = env->NewGlobalRef(dateObj);
    jobjectRefType type = env->GetObjectRefType(globalObj); // JNIGlobalRefType
    printf("%d\n", type); // 打印: 2

    jboolean res2 = env->IsSameObject(dateObj, globalObj); // true
    printf("%d\n", res2); // 打印: 1
}

8 JNI 与 Java 交互

8.1 属性和方法 ID

8.1.1 类型签名

        JNI 在调用 Java 方法时,需要通过方法签名定位到对应的方法。方法签名的定义如下。官方介绍详见 → type-signatures。补充:void 对应 V

// 对应的方法签名: ()V
void fun1()

// 对应的方法签名: (IF)D
double fun2(int a, float b)

// 对应的方法签名: (Ljava/lang/String;)V
void fun3(String s)

// 对应的方法签名: ([I)V
void fun4(int[] a)

// 对应的方法签名: ()[Ljava/lang/String;
String[] fun5()

// 对应的方法签名: (ILjava/lang/String;[I)J
long fun6(int n, String s, int[] arr)

8.1.2 获取属性和方法 ID

        属性和方法 ID 主要用于定位 Java 中的属性和方法位置,为调用相应属性或方法做好准备。它们的定义如下。官方介绍详见 → field-and-method-ids

struct _jfieldID;
typedef struct _jfieldID *jfieldID;

struct _jmethodID;
typedef struct _jmethodID *jmethodID;

        以下是 JNI 提供的常用属性和方法 ID 操作接口,官方介绍详见 → getfieldidgetstaticfieldidgetmethodidgetstaticmethodid

// 获取属性ID
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)
// 获取静态属性ID
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig)
// 获取方法ID
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)
// 获取静态方法ID
jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig)

        注意:无论是获取静态的 ID 还是非静态的 ID, 第一个参数都是 jclass,而不是 jobject。

        应用如下。

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass strClass = env->FindClass("java/lang/String");
    //String中定义了value:private final byte[] value;
    jfieldID field = env->GetFieldID(strClass, "value", "[B");
    jmethodID method = env->GetMethodID(strClass, "charAt", "(I)C");

    jclass intClass = env->FindClass("java/lang/Integer");
    jfieldID sField = env->GetStaticFieldID(intClass, "MIN_VALUE", "I");
    jmethodID sMethod = env->GetStaticMethodID(intClass, "max", "(II)I");
}

8.2 JNI 调用 Java

8.2.1 JNI 调用 Java 的属性

        以下是 JNI 提供的常用获取和设置属性接口,官方介绍详见 → gettypefieldgetstatictypefieldsettypefieldsetstatictypefield

// 获取属性
jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)

// 获取静态属性
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
jint GetStaticIntField(jclass clazz, jfieldID fieldID)
jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)

// 设置属性
void SetObjectField(jobject obj, jfieldID fieldID, jobject val)
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean val)
void SetByteField(jobject obj, jfieldID fieldID, jbyte val)
void SetCharField(jobject obj, jfieldID fieldID, jchar val)
void SetShortField(jobject obj, jfieldID fieldID, jshort val)
void SetIntField(jobject obj, jfieldID fieldID, jint val)
void SetLongField(jobject obj, jfieldID fieldID, jlong val)
void SetFloatField(jobject obj, jfieldID fieldID, jfloat val)
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble val)

// 设置静态属性
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)

        应用如下。

        Demo.java

public class Demo {
   static {
      System.loadLibrary("test");
   }

   public static void main(String[] args) {
      Data data = new Data();
      new Demo().test(data);
      data.print();
   }

   private native void test(Data data);
}

class Data {
   private static String strValue = "abc";
   private int intValue = 100;

   public void print() {
      System.out.println("----------Java----------");
      System.out.println("intValue=" + intValue + ", strValue=" + strValue + "\n");
   }
}

        说明:JNI 可以获取并修改 Java 的 private 属性;JNI 修改 Java 的 final 属性不会报错,但是也不会生效。

        test.cpp

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj, jobject data) {
    printf("----------JNI----------\n");
    jclass clazz = env->GetObjectClass(data);

    jfieldID field1 = env->GetFieldID(clazz, "intValue", "I");
    jint jIntValue = env->GetIntField(data, field1); // 获取属性
    printf("%d\n", jIntValue);

    env->SetIntField(data, field1, 234); // 设置属性
    
    jfieldID field2 = env->GetStaticFieldID(clazz, "strValue", "Ljava/lang/String;");
    jstring jStrValue = (jstring) env->GetStaticObjectField(clazz, field2); // 获取静态属性
    const char *c_str = env->GetStringUTFChars(jStrValue, NULL);
    printf("%s\n", c_str);
    env->ReleaseStringUTFChars(jStrValue, c_str);

    jstring newStr = env->NewStringUTF("xyz");
    env->SetStaticObjectField(clazz, field2, newStr); // 设置静态属性
}

        打印如下。

----------JNI----------
100
abc
----------Java----------
intValue=234, strValue=xyz

8.2.2 JNI 调用 Java 的方法

        以下是 JNI 提供的常用调用 Java 方法的接口,官方介绍详见 → calltypemethodcallnonvirtualtypemethodcallstatictypemethod

// 执行常规的虚方法调用, 遵循Java的多态规则 (调用实例的当前类或其子类中重写的方法)
void CallVoidMethod(jobject obj, jmethodID methodID, ...)
jobject CallObjectMethod(jobject obj, jmethodID methodID, ...)
jboolean CallBooleanMethod(jobject obj, jmethodID methodID, ...)
jbyte CallByteMethod(jobject obj, jmethodID methodID, ...)
jchar CallCharMethod(jobject obj, jmethodID methodID, ...)
jshort CallShortMethod(jobject obj, jmethodID methodID, ...)
jint CallIntMethod(jobject obj, jmethodID methodID, ...)
jlong CallLongMethod(jobject obj, jmethodID methodID, ...)
jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...)
jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...)

// 调用特定父类中的方法实现 (即使子类重写了该方法)
void CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
...

// 调用静态方法
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, ...)
jbyte CallStaticByteMethod(jclass clazz, jmethodID methodID, ...)
jchar CallStaticCharMethod(jclass clazz, jmethodID methodID, ...)
jshort CallStaticShortMethod(jclass clazz, jmethodID methodID, ...)
jint CallStaticIntMethod(jclass clazz, jmethodID methodID, ...)
jlong CallStaticLongMethod(jclass clazz, jmethodID methodID, ...)
jfloat CallStaticFloatMethod(jclass clazz, jmethodID methodID, ...)
jdouble CallStaticDoubleMethod(jclass clazz, jmethodID methodID, ...)

        说明:JNI 可以调用 Java 的 private 方法。 

        应用如下。

        Demo.java

public class Demo {
   static {
      System.loadLibrary("test");
   }

   public static void main(String[] args) {
      Work work = new Work();
      new Demo().test(work);
   }

   private native void test(Work work);
}

class Work {
   private void test1() {
      System.out.println("JAVA: test1");
   }

   private int test2(int a, int b) {
      System.out.println("JAVA: test2, a=" + a + ", b=" + b);
      return a + b;
   }

   private static String test3(int[] arr) {
      if (arr == null || arr.length == 0) {
         return "[]";
      }
      String str = "[" + arr[0];
      for (int i = 1; i < arr.length; i++) {
         str += ", " + arr[i];
      }
      str += "]";
      System.out.println("JAVA: test3, str=" + str);
      return str;
   }
}

        test.cpp

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj, jobject work) {
    jclass clazz = env->GetObjectClass(work);

    jmethodID method1 = env->GetMethodID(clazz, "test1", "()V");
    env->CallVoidMethod(work, method1, NULL); // 调用test1

    jmethodID method2 = env->GetMethodID(clazz, "test2", "(II)I");
    jint res2 = env->CallIntMethod(work, method2, 5, 7); // 调用test2
    printf("JNI: %d\n", res2);

    jsize size = 5;
    jint temp[] = {5, 3, 7, 2, 6};
    jintArray array = env->NewIntArray(size);
    env->SetIntArrayRegion(array, 0, size, temp);

    jmethodID method3 = env->GetStaticMethodID(clazz, "test3", "([I)Ljava/lang/String;");
    jstring res3 = (jstring) env->CallStaticObjectMethod(clazz, method3, array); // 调用test3
    const char *c_str = env->GetStringUTFChars(res3, NULL);
    printf("JNI: %s\n", c_str);
    env->ReleaseStringUTFChars(res3, c_str);
}

        打印如下。 

JAVA: test1
JAVA: test2, a=5, b=7
JNI: 12
JAVA: test3, str=[5, 3, 7, 2, 6]
JNI: [5, 3, 7, 2, 6]

9 异常处理

        以下是 JNI 提供的常用异常处理接口,官方介绍详见 → exceptions

// 检查是否有挂起的异常
jboolean ExceptionCheck()
// 获取异常对象
jthrowable ExceptionOccurred()
// 打印异常堆栈跟踪
void ExceptionDescribe()
// 清除当前异常
void ExceptionClear()
// 抛出新的异常
jint Throw(jthrowable obj)
// 使用指定类抛出新异常
jint ThrowNew(jclass clazz, const char *msg)

9.1 捕获异常

        捕获异常应用如下。

        Demo.java

public class Demo {
   static {
      System.loadLibrary("test");
   }

   public static void main(String[] args) {
      new Demo().test();
      System.out.println("JAVA: End");
   }

   private native void test();

   private void fun() {
      int a = 1 / 0; // 会抛出ArithmeticException异常
   }
}

        如果 JNI 中不处理 Java 中抛出的异常,JNI 中程序也会继续往下运行,直到回到调用该 JNI 接口的 Java 代码后才终止,如下。

        test.cpp

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass clazz = env->GetObjectClass(thisObj);
    jmethodID method = env->GetMethodID(clazz, "fun", "()V");
    env->CallVoidMethod(thisObj, method);
    printf("JNI: handle next logic\n");
}

        打印如下。

JNI: handle next logic
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Demo.fun(Demo.java:13)
        at Demo.test(Native Method)
        at Demo.main(Demo.java:7)

        JNI 中可以通过以下方式处理异常。

        test.cpp

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass clazz = env->GetObjectClass(thisObj);
    jmethodID method = env->GetMethodID(clazz, "fun", "()V");
    env->CallVoidMethod(thisObj, method);

    if (env->ExceptionCheck()) { // 检查是否有挂起的异常
        env->ExceptionDescribe(); // 打印异常堆栈跟踪
        env->ExceptionClear(); // 清除当前异常
    }

    printf("JNI: handle next logic\n");
}

        打印如下。 

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Demo.fun(Demo.java:14)
        at Demo.test(Native Method)
        at Demo.main(Demo.java:7)
JNI: handle next logic
JAVA: End

9.2 抛出异常

        抛出异常应用如下,java 代码同 8.1 节。

        test.cpp

#include <jni.h>
#include <stdio.h>
#include "Demo.h"

extern "C"
JNIEXPORT void JNICALL Java_Demo_test(JNIEnv *env, jobject thisObj) {
    jclass clazz = env->GetObjectClass(thisObj);
    jmethodID method = env->GetMethodID(clazz, "fun", "()V");
    env->CallVoidMethod(thisObj, method);

    jthrowable exception = env->ExceptionOccurred(); // 获取异常对象
    if (exception != NULL) {
        env->ExceptionClear(); // 清除当前异常
        env->Throw(exception); // 抛出新的异常
    } else {
        jclass exClazz = env->FindClass("java/lang/Exception");
        env->ThrowNew(exClazz, "Custom Exception"); // 使用指定类抛出新异常
    }

    printf("JNI: handle next logic\n");
}

        打印如下。

JNI: handle next logic
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Demo.fun(Demo.java:14)
        at Demo.test(Native Method)
        at Demo.main(Demo.java:7)

        如果将 Demo.java 中 fun 函数里 “1 / 0” 改为 “1 / 2”,将打印如下。

JNI: handle next logic
Exception in thread "main" java.lang.Exception: Custom Exception
        at Demo.test(Native Method)
        at Demo.main(Demo.java:7)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little_fat_sheep

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值