04-JNI函数

上一篇:03-JNI 类型和数据结构


本章是 JNI 函数的参考章节。它提供了所有 JNI 函数的完整列表。它还介绍了 JNI 函数表的具体布局。

注意:++使用 "必须 "一词来描述对 JNI 程序员的限制++ 。例如,++当你看到某个 JNI 函数必须接收一个非 NULL 对象时,你就有责任确保 NULL 不会传递给该 JNI 函数。因此,JNI 实现无需在该 JNI 函数中执行 NULL 指针检查++。在明确不允许的情况下传递 NULL 可能会导致意外异常或致命崩溃。

++定义为既可返回 NULL 又可在出错时抛出异常的函数,可以选择只返回 NULL 表示出错,但不抛出任何异常++ 。例如,JNI 实现可能认为 "内存不足 "是暂时的,因此可能不希望抛出 OutOfMemoryError ,因为这看起来是致命的(JDK API java.lang.Error 文档:"++表示严重问题,合理的应用程序不应尝试捕获++")。

本章部分内容改编自 Netscape 的 JRI 文档。

4.1 接口函数表

++每个函数都可以通过 JNIEnv 参数以固定偏移量访问++。JNIEnv 类型是一个指向存储所有 JNI 函数指针的结构的指针。其定义如下:

cpp 复制代码
typedef const struct JNINativeInterface *JNIEnv;

++虚拟机将初始化函数表++,如下代码示例所示。请注意,前三个条目是为将来与 COM 兼容而保留的。此外,我们还在函数表开头附近保留了一些额外的 NULL 条目,以便将来与类相关的 JNI 操作可以添加到 FindClass 之后(前),而不是表的末尾。

cpp 复制代码
const struct JNINativeInterface ... = {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    GetSuperclass,
    IsAssignableFrom,

    ToReflectedField,

    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,

    PushLocalFrame,
    PopLocalFrame,

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NewLocalRef,
    EnsureLocalCapacity,

    AllocObject,
    NewObject,
    NewObjectV,
    NewObjectA,

    GetObjectClass,
    IsInstanceOf,

    GetMethodID,

    CallObjectMethod,
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,

    CallNonvirtualObjectMethod,
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,

    GetFieldID,

    GetObjectField,
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,

    GetStaticMethodID,

    CallStaticObjectMethod,
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,

    GetStaticFieldID,

    GetStaticObjectField,
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,

    SetStaticObjectField,
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,

    NewString,

    GetStringLength,
    GetStringChars,
    ReleaseStringChars,

    NewStringUTF,
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,

    GetArrayLength,

    NewObjectArray,
    GetObjectArrayElement,
    SetObjectArrayElement,

    NewBooleanArray,
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,

    GetBooleanArrayElements,
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,

    ReleaseBooleanArrayElements,
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,

    GetBooleanArrayRegion,
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,

    RegisterNatives,
    UnregisterNatives,

    MonitorEnter,
    MonitorExit,

    GetJavaVM,

    GetStringRegion,
    GetStringUTFRegion,

    GetPrimitiveArrayCritical,
    ReleasePrimitiveArrayCritical,

    GetStringCritical,
    ReleaseStringCritical,

    NewWeakGlobalRef,
    DeleteWeakGlobalRef,

    ExceptionCheck,

    NewDirectByteBuffer,
    GetDirectBufferAddress,
    GetDirectBufferCapacity,

    GetObjectRefType,

    GetModule,

    IsVirtualThread
  };

4.2 常数

JNI API 中使用了许多通用常量。

4.2.1 布尔值

cpp 复制代码
#define JNI_FALSE 0
#define JNI_TRUE 1

4.2.2 返回值

JNI 函数的一般返回值常量。

cpp 复制代码
#define JNI_OK           0                 /* success */
#define JNI_ERR          (-1)              /* unknown error */
#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
#define JNI_EVERSION     (-3)              /* JNI version error */
#define JNI_ENOMEM       (-4)              /* not enough memory */
#define JNI_EEXIST       (-5)              /* VM already created */
#define JNI_EINVAL       (-6)              /* invalid arguments */

4.3 版本信息

4.3.1 获取版本

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引号:4
 * @param evn JNI 接口指针,不得为 NULL 。
 * @return 返回高 16 位的主版本号和低 16 位的次版本号。
 */
jint GetVersion(JNIEnv *env);

返回本地方法接口的版本。对于 Java SE Platform 21 及更高版本,它返回 JNI_VERSION_21 。下表列出了 Java SE 平台各版本中包含的 JNI 版本(++对于旧版本的 JNI,使用的是 JDK 版本而不是 Java SE 平台++):

4.3.2 版本常量

cpp 复制代码
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
#define JNI_VERSION_1_8 0x00010008
#define JNI_VERSION_9   0x00090000
#define JNI_VERSION_10  0x000a0000
#define JNI_VERSION_19  0x00130000
#define JNI_VERSION_20  0x00140000
#define JNI_VERSION_21  0x00150000

4.4 Class操作

4.4.1 DefineClass

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引: 5
 *  
 * @param env JNI 接口指针,不得为 NULL 。
 * @param name 要定义的类或接口的名称。该字符串以修改后的 UTF-8 编码。该值可以是 NULL ,也必须与类文件数据中编码的名称一致。
 * @param loader 分配给已定义类的类加载器。该值可以是 NULL ,表示 "空类加载器"(或 "引导类加载器")。
 * @param buf 包含1 个文件数据的缓冲区。 NULL 值将导致 ClassFormatError .
 * @param bufLen 缓冲区长度
 * @return jclass 返回一个 Java 类对象,如果出现错误,则返回 NULL 。
 * @throw ClassFormatError  如果类别数据没有指定有效的类别。
 * @throw ClassCircularityError 如果一个类或接口是它自己的超类或超接口。
 * @throw OutOfMemoryError  如果系统内存不足。
 * @throw SecurityException  如果调用者试图在 "java "包树中定义一个类。
 */
jclass DefineClass(JNIEnv *env, const char *name, 
    jobject loader, const jbyte *buf, jsize bufLen);

4.4.2 FindClass

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 6
 * 
 * @param env JNI 接口指针,不得为 NULL 
 * @param name 全称类名(即以 " / "分隔的包名,后面是类名)。如果名称以 " [ "(数组签名字符)开头,则返回一个数组类。
 *             字符串以修改后的 UTF-8 编码。如果数值为 NULL ,则可能导致 NoClassDefFoundError 或崩溃。
 * @return jclass 根据全称返回类对象,如果找不到该类,则返回 NULL 。
 * @throw ClassFormatError  如果类别数据没有指定有效的类别
 * @throw ClassCircularityError  如果一个类或接口是它自己的超类或超接口
 * @throw NoClassDefFoundError  如果找不到所请求的类或接口的定义
 * @throw OutOfMemoryError  如果系统内存不足
 */
jclass FindClass(JNIEnv *env, const char *name);

在 JDK 1.1 版中,该函数用于加载本地定义的类。它会在 CLASSPATH环境变量指定的目录和 zip 文件中搜索指定名称的类。

++自 JDK 1.2 起++,Java 安全模型允许非系统类加载和调用本地方法。 ++FindClass 会定位与当前本地方法相关的类加载器,即声明本地方法的类的类加载器++。++如果本地方法属于系统类,则不会涉及类加载器。否则,将调用适当的类加载器来加载、链接和初始化被命名的类++。

自 JDK 1.2 起,当通过调用接口调用 FindClass 时,++当前没有本地方法或其相关的类加载器。在这种情况下,将使用 ClassLoader.getSystemClassLoader的结果++ 。这是虚拟机为应用程序创建的类加载器,可以定位 java.class.path 属性中列出的类。

如果从库生命周期函数钩子调用 FindClass ,类加载器的确定方法如下:

①. 为 JNI_OnLoad 和 JNI_OnLoad_L 时,使用加载本地程序库的类的类加载器;

②. 对于 JNI_OnUnload 和 JNI_OnUnload_L ,使用由 ClassLoader.getSystemClassLoader 返回的类加载器(因为++加载时使用的类加载器可能已不存在++)

name 参数是全称类名或数组类型特征。例如, java.lang.String 类的全称是:

cpp 复制代码
"java/lang/String"

数组类 java.lang.Object[] 的数组类型签名是:

cpp 复制代码
"[Ljava/lang/Object;"

4.4.3 GetSuperclass

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 10
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @return jclass 返回 clazz 或 NULL 所代表的类的超类
 */
jclass GetSuperclass(JNIEnv *env, jclass clazz);

如果 clazz 表示除类 Object 之外的任何其他类,那么该函数将返回表示由 clazz 指定的类的超类的对象。++如果 clazz 表示类 Object ,或 clazz 表示接口,则此函数返回 NULL++ 。

4.4.4 IsAssignableFrom

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 11
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz1 第一类参数,不得为 NULL
 * @param clazz2 第二类参数,不得为 NULL
 * @return jboolean 如果以下任一条件为真,则返回 JNI_TRUE:
 *  ①. 第一个和第二个类参数指向同一个 Java 类
 *  ②. 第一类是第二类的子类
 *  ③. 第一个类的接口之一是第二个类
 */
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

确定 clazz1 的对象是否可以安全地转换为 clazz2 。

4.5 模块操作

4.5.1 GetModule

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 233
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @return jobject 返回类或接口所属的模块
 * @since JDK/JRE 9
 */
jobject GetModule(JNIEnv *env, jclass clazz);

返回该类所属模块的 java.lang.Module 对象。++如果类不在已命名的模块中,则返回类加载器的未命名模块++。如果类表示数组类型,则此函数返回元素类型的 Module 对象。如果类代表原始类型或 void ,则返回 java.base 模块的 Module 对象。

4.6 线程操作

4.6.1 IsVirtualThread

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 234
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,可能是 NULL 值
 * @return jboolean 如果对象是虚拟线程,则返回 JNI_TRUE
 * @since JDK/JRE 21
 */
jboolean IsVirtualThread(JNIEnv *env, jobject obj);

测试对象是否为虚拟线程。

4.7 异常情况

4.7.1 Throw

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 13
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 1 个对象,不得为 NULL
 * @return jint 成功时返回 0;失败时返回负值
 * @throw The java.lang.Throwable object obj
 */
jint Throw(JNIEnv *env, jthrowable obj);

抛出一个 java.lang.Throwable 对象。

4.7.2 ThrowNew

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 14
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz java.lang.Throwable 的子类,一定不是 NULL
 * @param message 用于构建 java.lang.Throwable 对象的信息。字符串以修改后的 UTF-8 编码。该值可以是 NULL;
 * @return jint 成功时返回 0;失败时返回负值。
 * @throw 新建的 java.lang.Throwable 对象
 */
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);

从指定的类中构造一个异常对象,该异常对象的信息由 message 指定,并导致该异常被抛出。

4.7.3 ExceptionOccurred

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 15
 *
 * @param env JNI 接口指针,不得为 NULL
 * @return jthrowable 返回当前正在抛出的异常对象,如果当前没有异常抛出,则返回 NULL
 */
jthrowable ExceptionOccurred(JNIEnv *env);

确定是否有异常抛出。++在本地代码调用 ExceptionClear() 或 Java 代码处理异常之前,异常不会被抛出++。

4.7.4 ExceptionDescribe

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 16
 * 
 * @param env JNI 接口指针,不得为 NULL
 */
void ExceptionDescribe(JNIEnv *env);

向系统错误报告通道(如 stderr )打印异常和堆栈回溯。++调用该函数的副作用是清除挂起异常++。这是一个用于调试的方便例程。

4.7.5 ExceptionClear

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 17
 *
 * @param env JNI 接口指针,不得为 NULL
 */
void ExceptionClear(JNIEnv *env);

清除当前抛出的任何异常。如果当前没有异常抛出,则此例程没有任何作用。

4.7.6 FatalError

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 18
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param msg 错误信息。字符串以修改后的 UTF-8 编码。可能是 NULL 值。
 */
void FatalError(JNIEnv *env, const char *msg);

引发致命错误,虚拟机无法恢复。此函数不会返回。

4.7.7 ExceptionCheck

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 228
 *
 * @param env JNI 接口指针,不得为 NULL
 * @return jboolean 当有异常等待处理时,返回 JNI_TRUE ;否则,返回 JNI_FALSE
 */
jboolean ExceptionCheck(JNIEnv *env);

我们引入了一个方便的函数来检查待处理的异常,而无需创建对异常对象的本地引用。

4.8 全局与局部引用

㈠. 全局引用

4.8.1 NewGlobalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 21
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 全局或局部引用。可以是 NULL 值,在这种情况下,该函数将返回 NULL
 * @return jobject 返回给定 obj 的全局引用, 如果出现以下情况,可能返回 NULL
 *  ①. obj 指向 null
 *  ②. 系统内存耗尽
 *  ③. obj 是一个弱全局引用,已被垃圾回收
 */
jobject NewGlobalRef(JNIEnv *env, jobject obj);

4.8.2 DeleteGlobalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 22
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param globalRef 全局引用。可能是 NULL 值,在这种情况下,该函数不执行任何操作
 */
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

删除 globalRef 指向的全局引用

㈡. 局部引用

本地引用在本地方法调用期间有效。++本地方法返回后,它们会被自动释放++ 。++每个本地引用都会耗费一定的 Java 虚拟机资源。程序员需要确保本地方法不会过度分配本地引用++。虽然本地引用会在本地方法返回 Java 后自动释放,但++过度分配本地引用可能会导致本地方法执行期间虚拟机内存不足++。

4.8.3 DeleteLocalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 23
 * 
 * @param env JNI 接口指针,不得为 NULL
 * @param localRef 本地引用。如果此处传递的值为 NULL ,则函数不执行任何操作
 */
void DeleteLocalRef(JNIEnv *env, jobject localRef);

删除 localRef 指向的本地引用。

注:JDK/JRE 1.1 提供了上述 DeleteLocalRef 函数,以便程序员手动删除本地引用。例如,++如果本地代码遍历一个可能很大的对象数组,并在每次迭代中使用一个元素,那么在下一次迭代中创建新的本地引用之前,删除不再使用的数组元素的本地引用是一个很好的做法++。从 JDK/JRE 1.2 开始,为本地引用生命周期管理提供了一组额外的函数。它们是下面列出的四个函数。

4.8.4 EnsureLocalCapacity

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 26
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param capacity 所需本地引用的最小数量。必须大于等于 0
 * @return jint 成功时为 JNI_OK
 * @since JDK/JRE 1.2
 */
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

确保在当前线程中至少可以创建给定数量的本地引用。成功时返回 0,否则返回负数并抛出 OutOfMemoryError 。

++在进入本地方法之前,虚拟机会自动确保至少可以创建 16 个本地引用++。

为了实现向后兼容,虚拟机分配的本地引用会超出确保的容量。(作为调试支持,虚拟机可能会向用户发出创建过多本地引用的警告)。在 JDK 中,程序员可以提供 -verbose:jni 命令行选项来打开这些信息)。如++果创建的本地引用不能超过保证容量,虚拟机就会调用 FatalError++ 。

某些 Java 虚拟机实现可能会选择限制最大值 capacity ,这可能会导致函数返回错误(如 JNI_ERR 或 JNI_EINVAL )。例如,HotSpot JVM 实现使用 -XX:+MaxJNILocalCapacity 标志(默认值:65536)。

4.8.5 PushLocalFrame

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 19
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param capacity 所需本地引用的最小数量。必须大于 0
 * @return jint 成功时为 JNI_OK
 * @since JDK/JRE 1.2
 */
jint PushLocalFrame(JNIEnv *env, jint capacity);

创建一个新的本地参照系,其中至少可以创建给定数量的本地参照系。成功时返回 0,失败时返回负数和待定的 OutOfMemoryError 。

++请注意,在以前的局部帧中创建的局部引用在当前局部帧中仍然有效++。

与 EnsureLocalCapacity 一样,某些 Java 虚拟机实现可能会选择限制最大值 capacity ,这可能会导致函数返回错误。

4.8.6 PopLocalFrame

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 20
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param result 传递给上一个本地参照系的对象,可以是 NULL
 * @return jobject 返回给定 result 对象在上一个局部参照系中的局部参照值,如果给定 result 对象是 NULL ,则返回 NULL
 * @since JDK/JRE 1.2
 */
jobject PopLocalFrame(JNIEnv *env, jobject result);

弹出当前局部参照系,释放所有局部参照,并返回给定 result 对象在上一个局部参照系中的局部参照。

++如果不需要返回上一帧的引用,则将 NULL 设为 result++ 。

4.8.7 NewLocalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 25
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param ref 对象引用,函数为其创建新的局部引用。可以是 NULL 值
 * @return jobject 返回一个新的本地引用,该引用指向与 ref 相同的对象。
 *  如果出现以下情况,可能返回 NULL:
 *  ①. ref 指 null
 *  ②. 系统内存耗尽
 *  ③. 系统内存耗尽
 * @since JDK/JRE 1.2
 */
jobject NewLocalRef(JNIEnv *env, jobject ref);

++创建一个新的局部引用,该引用指向与 ref 相同的对象++。给定的 ref 可以是全局引用、局部引用或 NULL 。如果 ref 指向 null ,则返回 NULL 。

4.9 全局弱引用

弱全局引用是一种特殊的全局引用。与普通全局引用不同,++弱全局引用允许对底层 Java 对象进行垃圾回收++。++弱全局引用可用于任何使用全局或局部引用的情况++。

弱全局引用与 Java 幻象引用 ( java.lang.ref.PhantomReference ) 有关。在确定对象是否为幻影可及时,指向特定对象的弱全局引用将被视为指向该对象的幻影引用(参见 java.lang.ref )。

++在垃圾回收器清除指向同一对象的 PhantomReference 的同时,这种弱全局引用的功能等同于 NULL++ 。

由于本地方法运行时可能会进行垃圾回收,因此弱全局引用所引用的对象可以随时被释放。++虽然可以在使用全局引用的地方使用弱全局引用,但一般来说不宜这样做,因为弱全局引用的功能可能会在不经意间等同于 NULL++ 。

IsSameObject可以用来比较弱全局引用和非 NULL 本地或全局引用。如果对象相同,只要另一个引用没有被删除,弱全局引用就不会在功能上等同于 NULL 。

IsSameObject也可用于将弱全局引用与 NULL 进行比较,以确定底层对象是否已被释放。不过,程序员不应该依赖这种检查来确定弱全局引用是否可以(作为非 NULL 引用)。在未来的任何 JNI 函数调用中使用,因为中间的垃圾回收可能会改变弱全局引用。

相反,建议使用 JNI 函数 NewLocalRefNewGlobalRef获取底层对象的(强)局部或全局引用。如果对象已被释放,这些函数将返回 NULL 。否则,新引用将阻止底层对象被释放。新引用(如果非 NULL )可用于访问底层对象,并在不再需要时删除。

4.9.1 NewWeakGlobalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 226
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 要创建全局弱引用的对象
 * @return jweak 返回给定 obj 的全局弱引用
 *  如果出现以下情况,可能返回 NULL:
 *  ①. obj 指 null
 *  ②. 系统内存耗尽
 *  ③. obj 是一个弱全局引用,已被垃圾回收
 * @throw OutOfMemoryError  如果系统内存耗尽
 * @since JDK/JRE 1.2
 */
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

创建一个新的弱全局引用。弱全局引用不会阻止对给定对象的垃圾回收。 IsSameObject可以用来测试引用所指向的对象是否已被释放。如果 obj 指向 null ,或 obj 是弱全局引用,或虚拟机内存耗尽,则返回 NULL 。如果虚拟机内存耗尽,将抛出 OutOfMemoryError 。

4.9.2 DeleteWeakGlobalRef

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 227
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 要删除的全局弱引用。如果通过 NULL ,则此函数不执行任何操作
 * @since JDK/JRE 1.2
 */
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

删除给定弱全局引用所需的虚拟机资源。

4.10 对象操作

4.10.1 AllocObject

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 27
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象的引用,不得为 NULL
 * @return jobject 返回一个 Java 对象,如果无法构建对象,则返回 NULL
 * @throw InstantiationException  如果类是接口或抽象类
 * @throw OutOfMemoryError  如果系统内存不足
 */
jobject AllocObject(JNIEnv *env, jclass clazz);

++分配一个新 Java 对象,但不调用该对象的任何构造函数++。返回对象的引用。

注:Java 语言规范 "Implementing Finalization"(JLS §12.6.1)规定"在对象Obj调用了对象的构造函数且调用成功之前,对象Obj不可最终确定"。++由于 AllocObject() 没有调用构造函数,因此使用该函数创建的对象不符合最终化的条件++。

++clazz 参数不得引用数组类++。

4.10.2 NewObject, NewObjectA, NewObjectV

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 28
 *  程序员会将所有要传递给构造函数的参数紧跟在 methodID 参数之后。
 *  NewObject() 会接受这些参数,并将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象的引用,不得为 NULL
 * @param methodID 构造函数的方法 ID
 * @param ... 构造函数的参数
 * @return jobject 返回一个 Java 对象,如果无法构建对象,则返回 NULL
 * @throw InstantiationException 如果类是接口或抽象类
 * @throw OutOfMemoryError 如果系统内存不足
 * @throw 构造函数抛出的任何异常
 */
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

/**
 * @brief JNIEnv 接口函数表中的索引 30
 *  程序员将所有要传递给构造函数的参数放入一个 args 数组 jvalues 中,该数组紧跟在参数 methodID 之后。
 *  NewObjectA() 会接受这个数组中的参数,并反过来将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象的引用,不得为 NULL
 * @param methodID 构造函数的方法 ID
 * @param args 构造函数的参数
 * @return jobject 返回一个 Java 对象,如果无法构建对象,则返回 NULL
 * @throw InstantiationException 如果类是接口或抽象类
 * @throw OutOfMemoryError 如果系统内存不足
 * @throw 构造函数抛出的任何异常*
 */
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);

/**
 * @brief JNIEnv 接口函数表中的索引 29
 *  程序员会将所有要传递给构造函数的参数放入紧跟 methodID 参数之后的 va_list 类型 args 参数中。
 *  NewObjectV() 会接受这些参数,并将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象的引用,不得为 NULL
 * @param methodID 构造函数的方法 ID
 * @param args 构造函数的参数
 * @return jobject 返回一个 Java 对象,如果无法构建对象,则返回 NULL
 * @throw InstantiationException 如果类是接口或抽象类
 * @throw OutOfMemoryError 如果系统内存不足
 * @throw 构造函数抛出的任何异常
 */
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

构造一个新的 Java 对象。++方法 ID 表示要调用的构造函数方法++。++该 ID 必须通过调用 GetMethodID() 获得,方法名称为 <init> ,返回类型为 void ( V )++。

4.10.3 GetObjectClass

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 31
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @return jclass 返回一个 Java 类对象
 */
jclass GetObjectClass(JNIEnv *env, jobject obj);

返回对象的类别。

4.10.4 GetObjectRefType

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 232
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 本地、全局或弱全局引用
 * @return jobjectRefType 见下文描述
 * @since JDK/JRE 1.6
 */
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);

返回参数 obj 所引用对象的类型。参数 obj 可以是本地引用、全局引用或弱全局引用,也可以是 NULL .

返回值:函数 GetObjectRefType 返回以下定义为 jobjectRefType 的枚举值之一:

cpp 复制代码
JNIInvalidRefType    = 0
JNILocalRefType      = 1
JNIGlobalRefType     = 2
JNIWeakGlobalRefType = 3

如果参数 obj 是弱全局引用类型,则返回值为 JNIWeakGlobalRefType。如果参数 obj 是全局引用类型,返回值将是 JNIGlobalRefType。如果参数 obj 是本地引用类型,则返回值为 JNILocalRefType。如果 obj 参数不是有效引用,则此函数的返回值为 JNIInvalidRefType。

++无效引用是指不是有效句柄的引用++ 。也就是说,++obj 指针地址指向的内存位置不是由 Ref 创建函数分配的,也不是由 JNI 函数返回的++。

因此, NULL将是无效引用, GetObjectRefType(env,NULL) 将返回 JNIInvalidRefType

另一方面,空引用(即指向空值的引用)将返回空引用最初创建时的引用类型。

++GetObjectRefType不能用于已删除的引用++;++由于引用通常是作为指向内存数据结构的指针来实现的,而内存数据结构有可能被虚拟机中的任何引用分配服务重复使用,因此一旦被删除, GetObjectRefType 将返回什么值并不明确++。

4.10.5 IsInstanceOf

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 32
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,可能是 NULL 值
 * @param clazz Java 类对象,不得为 NULL
 * @return jboolean 如果 obj 可以转换为 clazz ,则返回 JNI_TRUE ;否则返回 JNI_FALSE 。 NULL 对象可以转换为任何类
 */
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);

测试对象是否是类的实例。

4.10.6 IsSameObject

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 24
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param ref1 Java 对象,可以是 NULL
 * @param ref2 Java 对象,可以是 NULL
 * @return jboolean 如果 ref1 和 ref2 指向同一个 Java 对象或都是 NULL ,则返回 JNI_TRUE ;否则返回 JNI_FALSE
 */
jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);

测试两个引用是否指向同一个 Java 对象。

4.11 访问对象的字段

4.11.1 GetFieldID

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 94
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param name 以 0 结尾的修改后 UTF-8 字符串表示的字段名称,不得为 NULL
 * @param sig 字段签名以 0 结尾的修改后 UTF-8 字符串表示,不得为 NULL
 * @return jfieldID 返回字段 ID,如果操作失败则返回 NULL
 * @throw NoSuchFieldError 如果找不到指定字段
 * @throw ExceptionInInitializerError 如果类初始化器因异常而失败
 * @throw OutOfMemoryError 如果系统内存不足
 */
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

返回类的实例(非静态)字段的字段 ID。++字段由其名称和签名指定++。访问函数 Get<type>Field 和 Set<type>Field 系列使用字段 ID 来检索对象字段。

++GetFieldID() 会导致未初始化的类被初始化++;

GetFieldID() 不能用于获取数组的长度字段,请使用 GetArrayLength() ;

4.11.2 Get<type>Field Routines

cpp 复制代码
/**
 * @brief 该访问例程系列返回对象的实例(非静态)字段值。
 *        要访问的字段由调用 GetFieldID() 得到的字段 ID 指定
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param fieldID 有效的字段标识
 * @return NativeType 返回字段的内容
 */
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);

下表描述了获取<type>字段例程名称和结果类型。++应使用字段的 Java 类型替换 Get<type>Field 中的 type,或使用表中的某个实际例程名称,并使用该例程对应的本地类型替换 NativeType++。

JNIEnv 接口函数表中的索引:

4.11.3 Set<type>Field Routines

cpp 复制代码
/**
 * @brief 该系列访问例程设置对象的实例(非静态)字段值。
 *        要访问的字段由调用 GetFieldID() 得到的字段 ID 指定。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param fieldID 有效的字段标识
 * @param value 字段的新值
 */
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);

下表描述了 Set<type>Field 例程名称和值类型。++应使用字段的 Java 类型替换 Set<type>Field 中的 type++ ,或使用表中的某个实际例程名称,++并使用该例程的相应本地类型替换 NativeType++。

JNIEnv 接口函数表中的索引:

4.12 调用实例方法

从本地代码中调用方法时,应注意这些方法是否对调用者敏感。

4.12.1 GetMethodID

cpp 复制代码
/**
 * @brief 返回类或接口的实例(非静态)方法的方法 ID。
 *        该方法可能在 clazz 的一个超类中定义,并被 clazz 继承。方法由其名称和签名决定。
 *        JNIEnv 接口函数表中的索引 33
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param name 以 0 结尾的修改后 UTF-8 字符串表示的方法名称,不得为 NULL
 * @param sig 以 0 结尾的修改 UTF-8 字符串表示的方法签名,不得为 NULL
 * @return jmethodID 返回方法 ID,如果找不到指定方法,则返回 NULL
 * @throw NoSuchMethodError NoSuchMethodError
 * @throw ExceptionInInitializerError 如果类初始化器因异常而失败
 * @throw OutOfMemoryError 如果系统内存不足
 */
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

++GetMethodID() 会导致未初始化的类被初始化++。

++要获取构造函数的方法 ID,请提供 <init> 作为方法名称, void ( V ) 作为返回类型++。

4.12.2 Call<type>Method Routines, Call<type>MethodA Routines, Call<type>MethodV Routines

cpp 复制代码
/**
 * @brief 程序员将所有要传递给方法的参数都紧跟在 methodID 参数之后。
 *        调用方法例程接受这些参数,并将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param methodID 有效的方法 ID
 * @param ...
 * @return NativeType 返回调用 Java 方法的结果
 */
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);

/**
 * @brief 程序员将方法的所有参数放入一个 args 数组 jvalues 中,该数组紧随参数 methodID 之后。
 *        调用MethodA 例程接受该数组中的参数,然后将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param methodID 有效的方法 ID
 * @param args 参数数组
 * @return NativeType 返回调用 Java 方法的结果
 */
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);

/**
 * @brief 程序员将方法的所有参数放在紧跟 methodID 参数的 va_list 类型 args 参数中。
 *        调用MethodV 例程接受参数,然后将参数传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param methodID 有效的方法 ID
 * @param args 参数数组
 * @return NativeType NativeType 返回调用 Java 方法的结果
 */
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

这三个操作系列的方法都用于从本地方法中调用 Java 实例方法,它们的区别仅在于向所调用的方法传递参数的机制不同。

++这些操作系列根据指定的方法 ID 在 Java 对象上调用实例(非静态)方法。 methodID 参数必须通过调用 GetMethodID() 得到++。

++当这些函数用于调用私有方法和构造函数时,方法 ID 必须来自 obj 的实际类,而不是它的某个超类++。

JNIEnv 接口函数表中的索引:

4.12.3 CallNonvirtual<type>Method Routines, CallNonvirtual<type>MethodA Routines, CallNonvirtual<type>MethodV Routines

cpp 复制代码
/**
 * @brief 序员将所有要传递给方法的参数放在紧接着 methodID 参数的位置。
 *        CallNonvirtual方法例程接受这些参数,并将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param clazz Java 类,不得为 NULL
 * @param methodID 方法 ID
 * @param ...
 * @return NativeType 返回调用 Java 方法的结果
 */
NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);

/**
 * @brief 程序员会将方法的所有参数放入一个 args 数组 jvalues 中,该数组紧随参数 methodID 之后。
 *        CallNonvirtualMethodA 例程接受该数组中的参数,然后将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param clazz Java 类,不得为 NULL
 * @param methodID 方法 ID
 * @param args 参数数组
 * @return NativeType 返回调用 Java 方法的结果
 */
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);

/**
 * @brief 程序员将方法的所有参数放在紧跟 methodID 参数的 va_list 类型 args 参数中。
 *        CallNonvirtualMethodV 例程接受参数,然后将参数传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj Java 对象,不得为 NULL
 * @param clazz Java 类,不得为 NULL
 * @param methodID 方法 ID
 * @param args 1个va_list参数
 * @return NativeType 返回调用 Java 方法的结果
 */
NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);

这些操作系列根据指定的类和方法 ID 在 Java 对象上调用实例(非静态)方法。 methodID 参数必须通过在类 clazz 上调用 GetMethodID() 来获得。

++CallNonvirtual方法系列例程和 Call方法系列例程是不同的++。Call方法例程根据对象的类或接口调用方法,而 CallNonvirtual方法例程则根据类(由 clazz 参数指定)调用方法,而++方法 ID 就是从类中获取的。方法 ID 必须从对象的实际类或其超类中获取++。

下表根据结果类型描述了每个方法调用例程。在调用<type>方法时,应将 type 替换为所调用方法的 Java 类型(或使用表中的一个实际方法调用例程名称),并将 NativeType 替换为该例程的相应本地类型。

JNIEnv 接口函数表中的索引:

4.13 访问静态字段

4.13.1 GetStaticFieldID

cpp 复制代码
/**
 * @brief 返回类中静态字段的字段 ID。字段由其名称和签名指定。
 *        GetStatic<type>Field 和 SetStatic<type>Field 系列访问函数使用字段 ID 来检索静态字段。
 *        JNIEnv 接口函数表中的索引 144
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param name 以 0 结尾的修改后 UTF-8 字符串表示的静态字段名称,不得为 NULL
 * @param sig 字段签名以 0 结尾的修改后 UTF-8 字符串表示,不得为 NULL
 * @return jfieldID 返回字段 ID,如果找不到指定的静态字段,则返回 NULL
 * @throw NoSuchFieldError 如果找不到指定的静态字段
 * @throw ExceptionInInitializerError 如果类初始化器因异常而失败
 * @throw OutOfMemoryError 如果系统内存不足
 */
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

++GetStaticFieldID() 会导致未初始化的类被初始化++。

4.13.2 GetStatic<type>Field Routines

cpp 复制代码
/**
 * @brief 该访问例程系列返回对象静态字段的值。
 *        要访问的字段由字段 ID 指定,字段 ID 可通过调用 GetStaticFieldID() 得到
 * 
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param fieldID 有效的静态字段 ID
 * @return NativeType 返回静态字段的内容
 */
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);

该访问例程系列返回对象静态字段的值。要访问的字段由字段 ID 指定,字段 ID 可通过调用 GetStaticFieldID() 得到。

下表描述了获取例程名称和结果类型系列。++应将 GetStatic<type>Field 中的 type 替换为字段的 Java 类型或表中的实际静态字段访问例程名称之一,并将 NativeType 替换为该例程的相应本地类型++。

JNIEnv 接口函数表中的索引:

4.13.2 SetStatic<type>Field Routines

cpp 复制代码
/**
 * @brief 该访问例程系列用于设置对象静态字段的值。
 *        要访问的字段由字段 ID 指定,字段 ID 可通过调用 GetStaticFieldID() 得到。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param fieldID 有效的静态字段 ID
 * @param value 字段的新值
 */
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);

下表描述了设置例程名称和值类型。应将 SetStatic<type>Field 中的 type 替换为字段的 Java 类型或表中的实际设置静态字段例程名称之一,并将 NativeType 替换为该例程的相应本地类型。

JNIEnv 接口函数表中的索引:

4.14 调用静态方法

从本地代码中调用方法时,应注意这些方法是否对调用者敏感。

4.14.1 GetStaticMethodID

cpp 复制代码
/**
 * @brief 返回类中静态方法的方法 ID。方法由其名称和签名指定。
 *        JNIEnv 接口函数表中的索引 113
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param name 以 0 结尾的修改后 UTF-8 字符串表示的静态方法名称,不得为 NULL
 * @param sig 方法签名以 0 结尾的修改 UTF-8 字符串表示,不得为 NULL
 * @return jmethodID 返回方法 ID,如果操作失败则返回 NULL
 * @throw NoSuchMethodError 如果找不到指定的静态方法
 * @throw ExceptionInInitializerError 如果类初始化器因异常而失败
 * @throw OutOfMemoryError 如果系统内存不足
 */
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

GetStaticMethodID() 会导致未初始化的类被初始化。

4.14.2 CallStatic<type>Method Routines, CallStatic<type>MethodA Routines, CallStatic<type>MethodV Routines

cpp 复制代码
/**
 * @brief 程序员应将所有要传递给方法的参数紧跟在 methodID 参数之后。
 *        CallStatic方法例程接受这些参数,并将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param methodID 有效的静态方法 ID
 * @param ... 参数数组
 * @return NativeType 返回调用静态 Java 方法的结果
 */
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

/**
 * @brief 程序员应将方法的所有参数放入一个 args 数组 jvalues 中,该数组紧随参数 methodID 之后。
 *        CallStaticMethodA 例程接受该数组中的参数,然后将它们传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param methodID 有效的静态方法 ID
 * @param args 参数数组
 * @return NativeType 返回调用静态 Java 方法的结果
 */
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

/**
 * @brief 程序员应将方法的所有参数放在紧随 methodID 参数之后的 va_list 类型 args 参数中。
 *        CallStaticMethodV 例程接受参数,然后将参数传递给程序员希望调用的 Java 方法。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param methodID 有效的静态方法 ID
 * @param args 1个va_list参数
 * @return NativeType 返回调用静态 Java 方法的结果
 */
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

JNIEnv 接口函数表中的索引:

4.15 字符串操作

本规范不假定 JVM 内部如何表示 Java 字符串。这些操作返回的字符串:

cpp 复制代码
GetStringChars()
GetStringUTFChars()
GetStringRegion()
GetStringUTFRegion()
GetStringCritical()

++因不需要以 NULL 结尾。程序员应通过 GetStringLength() 或 GetStringUTFLength() 来确定缓冲区容量要求++。

4.15.1 NewString

cpp 复制代码
/**
 * @brief 用一个 Unicode 字符数组构造一个新的 java.lang.String 对象。
 *        JNIEnv 接口函数表中的索引 163
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param unicodeChars Unicode 字符串指针。可以是 NULL 值,在这种情况下, len 必须是 0
 * @param len Unicode 字符串的长度。可为 0
 * @return jstring 返回 Java 字符串对象,如果无法构造字符串,则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 */
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);

4.15.2 GetStringLength

cpp 复制代码
/**
 * @brief 返回 Java 字符串的长度(Unicode 字符数
 *        JNIEnv 接口函数表中的索引 164
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param string Java 字符串对象,不得为 NULL
 * @return jsize 返回 Java 字符串的长度
 */
jsize GetStringLength(JNIEnv *env, jstring string);

4.15.3 GetStringChars

cpp 复制代码
/**
 * @brief 返回指向字符串 Unicode 字符数组的指针。该指针在调用 ReleaseStringChars() 之前一直有效
 *        如果 isCopy 不是 NULL ,则在复制时将 *isCopy 设置为 JNI_TRUE ;如果不复制,则将 *isCopy 设置为 JNI_FALSE
 *        JNIEnv 接口函数表中的索引 165
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param string Java 字符串对象,不得为 NULL
 * @param isCopy 布尔值指针,可以是 NULL 值
 * @return const jchar* 返回指向 Unicode 字符串的指针,如果操作失败则返回 NULL
 */
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);

该指针在调用 ReleaseStringChars() 之前一直有效;

4.15.4 ReleaseStringChars

cpp 复制代码
/**
 * @brief 通知虚拟机本地代码不再需要访问 chars 。
 *        参数 chars 是使用 GetStringChars() 从 string 获取的指针
 *        JNIEnv 接口函数表中的索引 166
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param string Java 字符串对象,不得为 NULL
 * @param chars 指向 Unicode 字符串的指针,如之前由 GetStringChars()
 */
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);

4.15.5 NewStringUTF

cpp 复制代码
/**
 * @brief 用修改后的 UTF-8 编码字符数组构造一个新的 java.lang.String 对象
 *        JNIEnv 接口函数表中的索引 167
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param bytes 指向修改后的 UTF-8 字符串的指针,不得为 NULL
 * @return jstring 返回 Java 字符串对象,如果无法构造字符串,则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 */
jstring NewStringUTF(JNIEnv *env, const char *bytes);

4.15.6 GetStringUTFLength

cpp 复制代码
/**
 * @brief 返回字符串的修改后 UTF-8 表示形式的长度(以字节为单位)。
 *        JNIEnv 接口函数表中的索引 168
 * 
 * @param env JNI 接口指针,不得为 NULL 
 * @param string Java 字符串对象,不得为 NULL
 * @return jsize 返回字符串的 UTF-8 长度
*/
jsize GetStringUTFLength(JNIEnv *env, jstring string);

4.15.7 GetStringUTFChars

cpp 复制代码
/**
 * @brief 返回一个指针,指向以修改后的 UTF-8 编码表示字符串的字节数组。
 *        该数组在被 ReleaseStringUTFChars() 释放前一直有效。
 *        JNIEnv 接口函数表中的索引 169
 * 
 * @param env JNI 接口指针,不得为 NULL 
 * @param string Java 字符串对象,不得为 NULL
 * @param isCopy 布尔值指针,可以是 NULL 值
 * @return 返回指向修改后 UTF-8 字符串的指针;如果操作失败,则返回 NULL
*/
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);

如果 isCopy 不是 NULL ,则在复制时将 *isCopy 设置为 JNI_TRUE ;如果不复制,则将 *isCopy 设置为 JNI_FALSE 。

4.15.8 ReleaseStringUTFChars

cpp 复制代码
/**
 * @brief 通知虚拟机本地代码不再需要访问 utf 。
 *        参数 utf 是使用 GetStringUTFChars() 从 string 派生的指针。
 *        JNIEnv 接口函数表中的索引 170
 * 
 * @param env JNI 接口指针,不得为 NULL
 * @param string Java 字符串对象,不得为 NULL
 * @param utf 指向修改后的 UTF-8 字符串的指针,之前由 GetStringUTFChars()
*/
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);

注:在 JDK/JRE 1.1 中,程序员可在用户提供的缓冲区中获取原始数组元素。从 JDK/JRE 1.2 开始,本机代码可使用附加函数集在用户提供的缓冲区中获取 Unicode (UTF-16) 或修改后的 UTF-8 编码字符。请参阅下面的函数。

4.15.9 GetStringRegion

cpp 复制代码
/**
 * @brief 将 len 个从偏移量 start 开始的 Unicode 字符复制到给定的缓冲区 buf
 *        JNIEnv 接口函数表中的索引 220
 * 
 * @param env JNI 接口指针,不得为 NULL
 * @param str Java 字符串对象,不得为 NULL
 * @param start 要复制的字符串中第一个 unicode 字符的索引。必须大于或等于 0,且小于字符串长度(" GetStringLength() ")
 * @param len 要复制的 unicode 字符数。必须大于或等于零," start + len "必须小于字符串长度(" GetStringLength() ")
 * @param buf 复制字符串区域的 unicode 字符缓冲区。如果给定值 len > 0,则不能为 NULL
 * @throw StringIndexOutOfBoundsException 索引溢出时
 * @since JDK/JRE 1.2
*/
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

4.15.10 GetStringUTFRegion

cpp 复制代码
/**
 * @brief 将从偏移量 start 开始的 len 个 Unicode 字符转换为修改后的 UTF-8 编码,并将结果放入给定的缓冲区 buf 中
 *        JNIEnv 接口函数表中的索引 221
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param str Java 字符串对象,不得为 NULL
 * @param start 要复制的字符串中第一个 unicode 字符的索引。必须大于或等于零,且小于字符串长度
 * @param len 要复制的 unicode 字符数。必须大于 0,且 " start + len " 必须小于字符串长度(" GetStringLength() ")
 * @param buf 复制字符串区域的 unicode 字符缓冲区。如果给定值 len > 0,则不能为 NULL
 * @throw StringIndexOutOfBoundsException 索引溢出时
 * @since JDK/JRE 1.2
 */
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);

len 参数指定 unicode 字符数。修改后的 UTF-8 编码字符数可能大于给定的 len 参数。 GetStringUTFLength() 可以用来确定所需字符缓冲区的最大大小。

++由于本规范不要求生成的字符串副本以 NULL 结尾,因此建议在使用该函数前清除给定的字符缓冲区(例如 " memset() "),以便安全地执行 strlen()++ 。

4.15.11 GetStringCritical, ReleaseStringCritical

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 224
 *        GetStringChars
 *        ReleaseStringChars
 * @since JDK/JRE 1.2
 */
const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);

/**
 * @brief JNIEnv 接口函数表中的索引 225
 *        GetStringChars
 *        ReleaseStringChars
 * @since JDK/JRE 1.2
 */
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);

这两个函数的语义与现有的 Get/ReleaseStringChars 函数类似。如果可能,虚拟机将返回一个指向字符串元素的指针;否则,将进行复制。++不过,这些函数的使用方式有很大的限制++。在 Get/ReleaseStringCritical 调用所包围的代码段中,本地代码不得发出任意的 JNI 调用,也不得导致当前线程阻塞。

对 Get/ReleaseStringCritical 的限制与对 Get/ReleasePrimitiveArrayCritical 的限制类似。

4.6 Array操作

4.6.1 GetArrayLength

cpp 复制代码
/**
 * @brief 返回数组中元素的个数
 *        JNIEnv 接口函数表中的索引 171
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组对象,不得为 NULL
 * @return jsize 返回数组的长度
 */
jsize GetArrayLength(JNIEnv *env, jarray array);

4.6.2 NewObjectArray

cpp 复制代码
/**
 * @brief 构造一个新数组,其中包含 elementClass 类对象。所有元素的初始值都设置为 initialElement
 *        JNIEnv 接口函数表中的索引 172
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param length 数组大小,必须 >= 0
 * @param elementClass 数组元素类别,不得为 NULL
 * @param initialElement 初始化值,可以是 NULL 值
 * @return jobjectArray 返回一个 Java 数组对象,如果无法构造数组,则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 */
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

4.6.3 GetObjectArrayElement

cpp 复制代码
/**
 * @brief 返回 Object 数组的一个元素
 *        JNIEnv 接口函数表中的索引 173
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组,不得为 NULL
 * @param index  数组索引,必须大于等于 0 且小于数组长度(" GetArrayLength() ")
 * @return jobject 返回一个 Java 对象
 * @throw ArrayIndexOutOfBoundsException 如果 index 没有指定数组中的有效索引
 */
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

4.6.4 SetObjectArrayElement

cpp 复制代码
/**
 * @brief 设置 Object 数组的一个元素
 *        JNIEnv 接口函数表中的索引 174
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组,不得为 NULL
 * @param index 数组索引,必须大于等于 0 且小于数组长度(" GetArrayLength() ")
 * @param value 新值,可以是 NULL 值
 * @throw ArrayIndexOutOfBoundsException 如果 index 没有指定数组中的有效索引
 * @throw ArrayStoreException 如果 value 的类不是数组元素类的子类
 */
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

4.6.5 New<PrimitiveType>Array Routines

cpp 复制代码
/**
 * @brief 用于构造新的基元数组对象的一系列操作。下表描述了具体的基元数组构造函数。
 *        应将 New<PrimitiveType>Array 替换为该表中的一个实际原始数组构造函数例程名称,
 *        并将 ArrayType 替换为该例程的相应数组类型。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param length 数组长度,必须大于等于 0
 * @return ArrayType 返回一个 Java 数组,如果无法构造数组,则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 */
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);

JNIEnv 接口函数表中的索引:

4.6.6 Get<PrimitiveType>ArrayElements Routines

cpp 复制代码
/**
 * @brief 返回原始数组主体的函数族。在调用相应的 Release<PrimitiveType>ArrayElements() 函数之前,结果一直有效。
 *        由于返回的数组可能是 Java 数组的副本,因此在调用<PrimitiveType>ArrayElements() 之前,对返回数组所做的更改不一定会反映在原始数组中。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组对象,不得为 NULL
 * @param isCopy 布尔值指针,可以是 NULL 值
 * @return NativeType* 返回指向数组元素的指针,如果操作失败,则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 */
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);

JNIEnv 接口函数表中的索引:

4.6.7 Release<PrimitiveType>ArrayElements Routines

cpp 复制代码
/**
 * @brief 一系列函数,用于通知虚拟机本地代码不再需要访问 elems 。
 *        elems 参数是使用相应的 GetArrayElements() 函数从 array 派生的指针。如有必要,该函数会将对 elems 所做的所有更改复制回原始数组。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组对象,不得为 NULL
 * @param elems 数组元素指针,由之前的 GetArrayElements 调用返回
 * @param mode 释放模式: 0 、 JNI_COMMIT 或 JNI_ABORT
 */
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);

参数 mode 提供了如何释放数组缓冲区的信息。如果 elems 不是 array 中元素的副本,则 mode 没有影响。否则, mode 会产生如下影响,如下表所示:

在大多数情况下,程序员会将 "0 "作为 mode 参数,以确保"锁定"数组和复制数组的行为一致。其他选项为程序员提供了更多内存管理控制,使用时应格外小心。如果将 JNI_COMMIT 作为 mode 参数传递给 elems ,而 elems 是 array 中元素的副本,则应最后调用 ReleaseArrayElements,将 mode 参数传递为 "0 "或 JNI_ABORT ,以释放 elems 缓冲区。

①. 将 Release<PrimitiveType>ArrayElements 替换为下表中的一个实际原始数组处理程序名称。

②. 将 ArrayType 替换为相应的数组类型。

③. 将 NativeType 替换为该例程的相应本地类型

JNIEnv 接口函数表中的索引:

4.6.8 Get<PrimitiveType>ArrayRegion Routines

cpp 复制代码
/**
 * @brief 将原始数组的一个区域复制到缓冲区的函数族。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组,不得为 NULL
 * @param start 起始索引,必须大于或等于零,且小于数组长度( GetArrayLength() )
 * @param len 要复制的元素个数,必须大于或等于 0," start + len "必须小于数组长度(" GetArrayLength() ")
 * @param buf 目标缓冲区,不得为 NULL
 * @throw ArrayIndexOutOfBoundsException 如果区域中的一个索引无效
 */
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);

下表描述了特定的基元数组元素访问器。应进行以下替换:

①. 将 Get<PrimitiveType>ArrayRegion 替换为下表中的一个实际原始元素访问例程名称

②. 将 ArrayType 替换为相应的数组类型

③. 将 NativeType 替换为该例程的相应本地类型

JNIEnv 接口函数表中的索引:

4.6.9 Set<PrimitiveType>ArrayRegion Routines

cpp 复制代码
/**
 * @brief 从缓冲区拷贝回原始数组区域的函数族。
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组,不得为 NULL
 * @param start 起始索引,必须大于或等于零,且小于数组长度( GetArrayLength() )
 * @param len 要复制的元素个数,必须大于或等于 0," start + len "必须小于数组长度(" GetArrayLength() ")
 * @param buf 源缓冲区,不得为 NULL
 * @throw ArrayIndexOutOfBoundsException 如果区域中的一个索引无效
 */
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf);

下表描述了特定的基元数组元素访问器。您应进行以下替换:

①. 将 Set<PrimitiveType>ArrayRegion 替换为下表中的一个实际原始元素访问例程名称

②. 将 ArrayType 替换为相应的数组类型

③. 将 NativeType 替换为该例程的相应本地类型

JNIEnv 接口函数表中的索引:

注:程序员可使用 Get/Release<primitivetype>ArrayElements 函数获取指向原始数组元素的指针。如果虚拟机支持"锁定"功能,则会返回原始数据的指针;否则,就会创建一个副本。Get/Release<primitivetype>ArrayCritical 函数允许本地代码直接获取指向数组元素的指针,即使虚拟机不支持"锁定"。

4.6.10 GetPrimitiveArrayCritical, ReleasePrimitiveArrayCritical

cpp 复制代码
/**
 * @brief 获取原始数组临界值
 *        JNIEnv 接口功能表中的链接索引 222
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array JNI 接口指针,不得为 NULL
 * @param isCopy 布尔值指针,可以是 NULL 值
 * @return void* 返回指向数组元素的指针,如果操作失败则返回 NULL
 * @throw OutOfMemoryError 如果系统内存不足
 * @since JDK/JRE 1.2
 */
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);

/**
 * @brief 释放原始数组临界值
 *        JNIEnv 接口功能表中的链接索引 223
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param array Java 数组,不得为 NULL
 * @param carray 由 GetPrimitiveArrayCritical 返回的临界数组指针
 * @param mode 释放模式(参见原始码阵释放模式): 0 , JNI_COMMIT 或 JNI_ABORT 。如果 carray 不是复制,则忽略
 * @since JDK/JRE 1.2
 */
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

这两个函数的语义与现有的 Get/Release<primitivetype>ArrayElements 函数非常相似。如果可能,虚拟机将返回指向原始数组的指针;否则,将进行复制。不过,这些函数的使用方式有很大的限制。

++在调用 GetPrimitiveArrayCritical 之后,本地代码不应在调用 ReleasePrimitiveArrayCritical 之前长时间运行++ 。我们必须将这对函数中的代码视为运行在 "临界区 "中。++在临界区内,本地代码不得调用其他 JNI 函数或任何可能导致当前线程阻塞并等待其他 Java 线程的系统调用++。(例如,当前线程不得调用另一个 Java 线程正在写入的流上的 read )。

这些限制使得本地代码更有可能获得数组的未拷贝版本,即使虚拟机不支持"锁定"。例如,当本地代码持有通过 GetPrimitiveArrayCritical 获取的数组指针时,虚拟机可能会暂时禁用垃圾回收。

可以嵌套多个 GetPrimtiveArrayCritical 和 ReleasePrimitiveArrayCritical 对。例如:

cpp 复制代码
jint len = (*env)->GetArrayLength(env, arr1);
jbyte *a1 = (*env)->GetPrimitiveArrayCritical(env, arr1, 0);
jbyte *a2 = (*env)->GetPrimitiveArrayCritical(env, arr2, 0);
/* We need to check in case the VM tried to make a copy. */
if (a1 == NULL || a2 == NULL) {
    ... /* out of memory exception thrown */
}
memcpy(a1, a2, len);
(*env)->ReleasePrimitiveArrayCritical(env, arr2, a2, 0);
(*env)->ReleasePrimitiveArrayCritical(env, arr1, a1, 0);

需要注意的是,如果虚拟机内部以不同的格式表示数组, GetPrimitiveArrayCritical 仍有可能复制数组。因此,我们需要对照 NULL 检查其返回值,以防可能出现的内存不足情况。

4.7 注册本地方法

4.7.1 RegisterNatives

cpp 复制代码
/**
 * @brief JNIEnv 接口函数表中的索引 215
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @param methods 类中的本地方法,不得为 NULL
 * @param nMethods 类中本地方法的数量,必须大于零
 * @return jint 成功时返回 "0";失败时返回负值
 * @throw NoSuchMethodError 如果找不到指定方法或该方法不是本地方法
 */
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

用 clazz 参数指定的类注册本地方法。++参数 methods 指定了一个由 JNINativeMethod个结构组成的数组,其中包含本地方法的名称、签名和函数指针++。++JNINativeMethod 结构的 name 和 signature 字段是指向修改后的 UTF-8 字符串的指针。 nMethods 参数指定数组中本地方法的数量++。 JNINativeMethod 结构定义如下:

cpp 复制代码
typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

函数指针名义上必须具有以下签名:

cpp 复制代码
ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);

请注意,++"0"可以通过更改特定本地 Java 方法要执行的本地代码来改变 JVM 的文档行为(包括加密算法、正确性、安全性和类型安全性)++。因此,请谨慎使用使用 RegisterNatives 功能的本地库应用程序。

4.7.2 UnregisterNatives

cpp 复制代码
/**
 * @brief 取消注册类的本地方法。类将回到其本地方法函数被链接或注册前的状态
 *        JNIEnv 接口函数表中的索引 216
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param clazz Java 类对象,不得为 NULL
 * @return jint 成功时返回 "0";失败时返回负值
 */
jint UnregisterNatives(JNIEnv *env, jclass clazz);

++该函数不应在普通本地代码中使用++。相反,它为特殊程序提供了一种重新加载和重新链接本地程序库的方法。

4.8 监控操作

4.8.1 MonitorEnter

cpp 复制代码
/**
 * @brief 进入与 obj 所引用的底层 Java 对象相关联的监视器
 *        JNIEnv 接口函数表中的索引 217
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 普通 Java 对象或类对象,不得为 NULL
 * @return jint 成功时返回 "0";失败时返回负值
 */
jint MonitorEnter(JNIEnv *env, jobject obj);

输入与 obj 所指对象相关的监视器。 obj 引用必须不是 NULL 。

每个 Java 对象都有一个与之关联的监视器。如果当前线程已拥有与 obj 关联的监视器,则会递增监视器中的计数器,以显示该线程进入监视器的次数。如果与 obj 关联的监视器未被任何线程拥有,则当前线程将成为该监视器的所有者,并将该监视器的进入计数设为 1。 如果与 obj 关联的监视器已被其他线程拥有,则当前线程将等待监视器被释放,然后再次尝试获得所有权。

通过 MonitorEnter JNI 函数调用进入的监控器不能使用 monitorexit Java 虚拟机指令或同步方法返回退出。 MonitorEnter JNI 函数调用和 monitorenter Java 虚拟机指令可能会竞相进入与同一对象相关的监控器。

++为避免死锁,通过 MonitorEnter JNI 函数调用进入的监视器必须使用MonitorExit JNI 调用退出,除非 DetachCurrentThread 调用用于隐式释放 JNI 监视器++。

4.8.2 MonitorExit

cpp 复制代码
/**
 * @brief 当前线程必须是与 obj 所引用的底层 Java 对象相关联的监视器的所有者。
 *        线程会递减计数器,以显示进入该监视器的次数。如果计数器的值为零,则当前线程释放监视器。
 *        JNIEnv 接口函数表中的索引 218
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param obj 普通 Java 对象或类对象,不得为 NULL
 * @return jint 成功时返回 "0";失败时返回负值
 * @throw IllegalMonitorStateException 如果当前线程不拥有监视器
 */
jint MonitorExit(JNIEnv *env, jobject obj);

本地代码不得使用 MonitorExit 退出通过同步方法或 monitorenter Java 虚拟机指令进入的监控器。

4.9 支持NIO

与 NIO 相关的入口点允许本地代码访问*java.nio* 直接缓冲区。++直接缓冲区的内容有可能位于普通垃圾堆之外的本地内存中++。有关直接缓冲区的信息,请参阅 NIO 包中的缓冲区和 java.nio.ByteBuffer 类的规范。

有三个函数允许 JNI 代码创建、检查和操作直接缓冲区:

cpp 复制代码
NewDirectByteBuffer
GetDirectBufferAddress
GetDirectBufferCapacity

Java 虚拟机的每个实现都必须支持这些函数,但并非每个实现都必须支持 JNI 对直接缓冲区的访问。如果 JVM 不支持此类访问,那么 NewDirectByteBuffer 和 GetDirectBufferAddress 函数必须始终返回 NULL , GetDirectBufferCapacity 函数必须始终返回 -1 。如果 JVM 支持这种访问,则必须实现这三个函数以返回相应的值。

4.9.1 NewDirectByteBuffer

cpp 复制代码
/**
 * @brief 分配并返回一个直接指向内存块的 java.nio.ByteBuffer ,该内存块从内存地址 address 开始,扩展 capacity 个字节。
 *        返回缓冲区的字节顺序始终是大三位(高字节在前; java.nio.ByteOrder.BIG_ENDIAN )。
 *        JNIEnv 接口函数表中的索引 229
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param address 内存区域的起始地址,不得为 NULL
 * @param capacity 内存区域的大小(以字节为单位),必须为非负数且小于或等于 Integer.MAX_VALUE
 * @return jobject 返回新创建的 java.nio.ByteBuffer 对象的本地引用。如果出现异常,或该虚拟机不支持 JNI 访问直接缓冲区,则返回 NULL
 * @throw IllegalArgumentException 如果 capacity 为负数或大于 Integer.MAX_VALUE
 * @throw OutOfMemoryError 如果 ByteBuffer个对象的分配失败
 * @since JDK/JRE 1.4
 */
jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);

4.9.2 GetDirectBufferAddress

cpp 复制代码
/**
 * @brief 获取并返回给定指令 java.nio.Buffer 引用的内存区域的起始地址
 *        JNIEnv 接口函数表中的索引 230
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param buf 直接 java.nio.Buffer 对象,不得 NULL
 * @return void* 返回缓冲区引用的内存区域的起始地址。如果内存区域未定义,或者给定对象不是直接 java.nio.Buffer ,或者该虚拟机不支持 JNI 访问直接缓冲区,则返回 NULL
 * @since JDK/JRE 1.4
 */
void* GetDirectBufferAddress(JNIEnv* env, jobject buf);

该函数允许本地代码访问 Java 代码通过缓冲区对象访问的同一内存区域。

4.9.3 GetDirectBufferCapacity

cpp 复制代码
/**
 * @brief 获取并返回给定指令 java.nio.Buffer 所引用的内存区域的容量。容量是内存区域包含的元素数量
 *        JNIEnv 接口函数表中的索引 231
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param buf 直接 java.nio.Buffer 对象,不得 NULL
 * @return jlong 返回与缓冲区相关联的内存区域的容量。如果给定对象不是直接 java.nio.Buffer ,
 *         如果对象是未对齐视图缓冲区且处理器架构不支持未对齐访问,或者该虚拟机不支持 JNI 访问直接缓冲区,则返回 -1
 * @since JDK/JRE 1.4
 */
jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);

4.10 支持反射

如果程序员知道 Java 方法或字段的名称和类型,就可以使用 JNI 调用 Java 方法或访问 Java 字段。Java Core Reflection API 允许程序员在运行时反射 Java 类。JNI 提供了一组转换函数,将 JNI 中使用的字段和方法 ID 转换为 Java Core Reflection API 中使用的字段和方法对象。

4.10.1 FromReflectedMethod

cpp 复制代码
/**
 * @brief 将 java.lang.reflect.Method 或 java.lang.reflect.Constructor 对象转换为方法 ID
 *        JNIEnv 接口函数表中的索引 7
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param method java.lang.reflect.Method 或 java.lang.reflect.Constructor 的对象,不得为 NULL
 * @return jmethodID 与给定 Java 反射方法相对应的 JNI 方法 ID,如果操作失败则为 NULL
 * @since JDK/JRE 1.2
 */
jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

4.10.2 FromReflectedField

cpp 复制代码
/**
 * @brief 将 java.lang.reflect.Field 转换为字段 ID。
 *        JNIEnv 接口函数表中的索引 8
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param field 1 个java.lang.reflect.Field对象,不得为 NULL
 * @return jfieldID 与给定 Java 反射 field 相对应的 JNI 字段 ID,如果操作失败,则为 NULL 。
 * @since JDK/JRE 1.2
 */
jfieldID FromReflectedField(JNIEnv *env, jobject field);

4.10.3 ToReflectedMethod

cpp 复制代码
/**
 * @brief 将从 cls 派生的方法 ID 转换为 java.lang.reflect.Method 或 java.lang.reflect.Constructor 对象。
 *        如果方法 ID 指向静态字段, isStatic 必须设为 JNI_TRUE ,否则设为 JNI_FALSE
 *        JNIEnv 接口函数表中的索引 9
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param cls Java 类对象,不得为 NULL
 * @param methodID 方法 ID,不得为 NULL
 * @param isStatic 表示给定的 methodID 是否为静态方法
 * @return jobject 返回与给定的 methodID 相对应的 java.lang.reflect.Method 或 java.lang.reflect.Constructor 的实例,如果操作失败,则返回 NULL
 * @since JDK/JRE 1.2
 */
jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);

如果失败,则抛出 OutOfMemoryError 并返回 0。

4.10.4 ToReflectedField

cpp 复制代码
/**
 * @brief 将从 cls 派生的字段 ID 转换为 java.lang.reflect.Field 对象。
 *        如果 fieldID 指向静态字段,则 isStatic 必须设置为 JNI_TRUE ,否则为 JNI_FALSE 。
 *        JNIEnv 接口函数表中的索引 12
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param cls Java 类对象,不得为 NULL
 * @param fieldID 字段 ID,不得为 NULL
 * @param isStatic 表示给定的 fieldID 是否为静态字段
 * @return jobject 返回与给定的 fieldID 相对应的 java.lang.reflect.Field 的实例,如果操作失败,则返回 NULL
 * @since JDK/JRE 1.2
 */
jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);

如果失败,则抛出 OutOfMemoryError 并返回 0。

4.11 虚拟机接口

4.11.1 GetJavaVM

cpp 复制代码
/**
 * @brief 返回与当前线程相关的 Java VM 接口(用于调用 API)。
 *        结果将放置在第二个参数 vm 所指向的位置
 *        JNIEnv 接口函数表中的索引 219
 *
 * @param env JNI 接口指针,不得为 NULL
 * @param vm 指向结果放置位置的指针,不得为 NULL
 * @return jint 成功时返回 "0";失败时返回负值。
 */
jint GetJavaVM(JNIEnv *env, JavaVM **vm);

下一篇: 05-调用API

相关推荐
西海天际蔚蓝5 分钟前
递归查询全量分页数据问题
java
俎树振18 分钟前
深入理解与优化Java二维数组:从定义到性能提升的全面指南
java·算法
DARLING Zero two♡26 分钟前
【优选算法】Sliding-Chakra:滑动窗口的算法流(上)
java·开发语言·数据结构·c++·算法
l and27 分钟前
Git 行尾换行符,导致无法进入游戏
android·git
程序媛小果29 分钟前
基于Django+python的Python在线自主评测系统设计与实现
android·python·django
hjxxlsx30 分钟前
二维数组综合
c++·算法
love静思冥想31 分钟前
Apache Commons ThreadUtils 的使用与优化
java·线程池优化
君败红颜32 分钟前
Apache Commons Pool2—Java对象池的利器
java·开发语言·apache
意疏41 分钟前
JDK动态代理、Cglib动态代理及Spring AOP
java·开发语言·spring
小王努力学编程43 分钟前
【C++篇】AVL树的实现
java·开发语言·c++