JNI 开发笔记 - Native代码开发以及核心API介绍

本文详细介绍了JNI中定义和注册Native方法的两种方式,以及JNI方法的使用,包括Get ID、调用Static/Non-Static方法、Get/Set Static/Non-Static Field等核心API的运用。此外,还讲解了Get/Release Array Elements、Global Weak和Local Reference等操作,以及在Native代码中处理String的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义和注册Native方法

本章介绍定义和注册Native方法的相关知识。在开发Native代码之前,先创建一个Java Class,在这个Class中声明与Native代码中对应的方法:

package com.examp.jni;
class HelloJNI {
     static {
         System.loadLibrary("hello-jni");
     }

     public native boolean hello();
}

定义Native方法


定义Native的方法有两种,区别于是映射Native方法与java方法的方式。如果使用函数命名规则来匹配和映射,则只需定义Native方法,JVM在Load Library时通过命名规则去搜索匹配的Native方法;如果采用运行时动态注册(使用RegisterNatives)的方式则需要先定义Native方法然后在JNI_OnLoad方法内注册NativeMethods。

因为JNI中Native方法与Java方法的映射可通过函数名规则来匹配,如果使用这种方法,可使用javah工具生成C/C++头文件。

    javac HelloJni.java
    javah HelloJni

当然,如果你熟悉JNI函数名映射规则,你可以自己手动创建。使用函数名称来映射Native方法与Java方法的规则如下:

JNIEXPORT ret-type JNICALL Java_{package-and-classname}_{function-name}(jni args)

JNIEXPORT jboolean JNICALL Java_com_examp_jni_HelloJNI_hello(JNIEnv *env, jobject obj);

JNI方法的形参列表至少包含JNIEnv *, jobjcet两个参数:
* JNIEnv *env: JNI Environment的引用,是用env便可以调用所有JNI提供的函数。
* jobjcet obj: 调用该JNI函数的对象的引用。

方法可以有更多的参数,如果有,紧跟以上两个参数后面即可。

注册Native方法


在JNI Overview章节中我们提到,除了使用函数名称映射Native方法与Java方法,还可以在运行时动态注册。使用动态注册的方式简化了函数名称,并且可以动态的更新映射关系。动态创建映射关系步骤如下:

1.在Native代码中定义方法

  jboolean hello() {
    // do something
    return JNI_TRUE;
  }

2.创建JNINativeMethod数组

  static JNINativeMethod jniMethods[] = {
    {"hello", "()Z", (void *)hello},
  };

应该按照以上格式将所有需要动态注册的方法填充至JNINativeMethod数组,JNINativeMethod结构有三个成员:
* const char *name: Java中声明的native方法。
* const char *signature:方法的签名。
* void *fnPtr: 函数指针

3.在JNI_OnLoad中调用RegisterNatives方法注册Natives方法到JVM,建立映射关系。

int JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        return JNI_ERR;
    }

    jclass cls = (*env)->FindClass(env, "LHelloJNI");
    if (cls == NULL)
        return JNI_ERR;

    int len = sizeof(jniMethods) / sizeof(jnimethods[0]);
    (*env)->RegisterNatives(env, cls, jniMethods, len);

    return JNI_VERSION_1_4;
}

JNI方法使用指导

Get ID


很多应用场景下,我们需要读写Java中的成员变量或调用Java中成员函数,实现这一功能的第一步是先或者目标(Java变量或函数)的ID。 JNI Environment提供一组用于获取这些ID的接口。

GetFieldID, GetStaticFieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 

GetFieldID返回非静态Java成员变量的ID,通过name和签名来匹配成员变量。GetStaticFieldID返回静态的Java变量的ID。失败返回NULL。

调用这两个方法均会导致未初始化的类被初始化。

GetMethodID, GetStaticMethodID

jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

GetMethodID返回类的非静态成员方法的ID,GetStaticMethodID返回类的静态方法。失败返回NULL。

调用这两个方法均会导致未初始化的类被初始化。

Call Static/Non-Static method


Native调用Java类的Static/Non-Static方法需通过以下6个JNI方法实现。

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

NativeType Call<type>Method(JNIEnv *env, jobject  obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject  obj, jmethodID methodID, jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject  obj, jmethodID methodID, va_list args);

Type类型有:Object、Boolean、Byte、Char、Short、Int、Long、Float、Double、Void。注意type指的是返回值的类型。

Static: Method ID对应的方法必须是clazz类或者其派生类的方法,不能是其父类的方法。
Non-Static: Method ID对应的方法需是obj对象所在类或者其派生类的方法,不能是其父类的方法。

以上三个JNI接口方法均用于调用非静态的Java方法,区别在于传递参数的形式不同。使用CallMethod需将所有参数完整列出;使用CallMethodA需将参数保存在jvalue*列表里;使用CallMethodV需将参数保存在va_list结构中。

可以看到Call Static Method和Call Nonstatic Method接口函数的第二个形参类型不同,因为static方法不需要有实际对象也可以调用,所以前者是jcalss后者是jobjcet。

Get/Set Static/Non-Static Field


Native对应Java成员变量的操作可通过以下4组JNI方法实现:

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);

void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);

Type类型有:Object、Boolean、Byte、Char、Short、Int、Long、Float、Double、Void。

Get/Release Array Elements


在Native层需要与Java层通过数据交互数据的应用场景下,JNI提供如下的方法来获得和释放对Java数组的引用。

NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);

PrimitiveType:Boolean,Byte, Char, Short, Int, Long, Float, Double。

Get Array Elements

Get方法用于获取对ArrayType的引用,isCopy指示JVM是否创建一份数组的拷贝,如果isCopy不为NULL,当成功创建拷贝的时候isCopy被置为JNI_TRUE,否则被置为JNI_FALSE。如果创建了数组的拷贝,则Native代码对数组内容的修改将会在调用Release方法后才会生效。如果isCopy为NULL,则JVM返回实际数组的引用。

Release Array Elements

Release方法通知JVM Native代码不再需要操作elems,elems是通过Get方法获得ArrayType引用的指针,JVM将根据参数决定是否要将elems中的修改写回到Java数组中以及如何释放缓冲区。

Mode参数指示如何释放缓冲区,它有如下三种值,在大多数应用场景下传递0使得对数组的修改生效且释放elems缓冲区。

mode          actions
0             copy back the content and free the elems buffer
JNI_COMMIT    copy back the content but do not free the elems buffer
JNI_ABORT     free the buffer without copying back the possible changes

Note:Get方法与Release方法需成对出现,因为每调用一次Get方法JVM将创建一份Local Reference,JVM能否创建的Local Reference的数量是有限的,如果大量调用Get方法而不调用Release方法将会导致溢出,JVM会抛出异常致使应用程序停止。

Get/Set Array Region


如果只需要重Java数组中复制数据到Native数组中,则使用GetArrayRegion,这将省去Release操作。

void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf);

PrimitiveType:Boolean,Byte, Char, Short, Int, Long, Float, Double。

Get方法:

参数array是待Copy的java数组,通常由Java代码通过形参传递到Native方法;start即Copy的起始索引,NativeType是Copy的目的地址。JNI将Copy array数组中的数据到buf所指向的内存里。

Set方法:

参数array Set操作的目标数组,通常由Java代码通过形参传递到Native方法;start即Copy的起始索引,NativeType是Copy的目的地址。JNI将把buf所指向的内存中的数据Copy到array数组中start位置。

Note: Get Array Region方法不需要任何Release操作。

Global Weak and Local Reference


当Native代码需要获得Java对象的引用时,则需要使用以下JNI方法。

jobject NewGlobalRef(JNIEnv *env, jobject obj);
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
jobject NewLocalRef(JNIEnv *env, jobject ref);
void DeleteLocalRef(JNIEnv *env, jobject localRef);
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

以上API用于创建Global/Local/Weak引用。New操作返回对象的引用,或者返回NULL代表对象已经被释放掉了。

三种类型的区别:

  • Global:全局有效,必须显示的Dlete,否则对象将一直不会被JVM回收。
  • Local:Local引用只在调用Native方法期间有效,虽然Local Reference会在Native方法调用结束后被释放掉,但任然推荐显示的释放Local Reference。
  • Weak:弱全局引用是特殊的全局引用,弱全局引用不增加的引用计数,允许被引用的对象随时被JVM回收掉。因此弱全局引用在使用的时候应先调用IsSameObject与NULL比较,若为NULL则代表对象已经被JVM回收掉了。

虽然IsSameObject可以用于判断WakeGlobalRef引用的对象是否已经被回收,但它无法阻止JVM回收对象,因此开发人员不应该依赖此方法来决定WakeGlobalRef是否可用,对象任然可能在任意时刻被JVM回收掉。

官方推荐的做法是:在使用WakeGlobalRef之前先使用GlobalRef或者LocalRef获得对象的引用,如果成功获得(返回值不为NULL)则将阻止JVM回收该对象。

String Operations


Native代码中可以针对String做一些操作,包括:

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);
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);
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
jsize GetArrayLength(JNIEnv *env, jarray array);

Note: Java字符和字符串(String,Char)使用Unicode编码,16bits,而C/C++中字符串使用UTF-8编码,8bits,因此在使用时需注意转换。

参考资料:

Oracle JNI Guide

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值