1 C 和 C++ 在 JNI 中的區(qū)別
JNI環(huán)境搭建 中介紹了在命令行和 Android Studio 中如何編譯 JNI 代碼,本文將介紹 JNI 的基礎(chǔ)語(yǔ)法,主要介紹 JNI 的數(shù)據(jù)類(lèi)型、JNI 與 Java 交互、異常處理,參考了 JNI 官方文檔,源碼詳見(jiàn)以下文件。
JDK\include\jni.h
SDK\ndk\27.2.12479018\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\jni.h
為了避免每個(gè)知識(shí)點(diǎn)都要介紹一遍 C 和 C++ 的在 JNI 中用法,本文先介紹下 C 和 C++ 在 JNI 中的區(qū)別,然后每個(gè)知識(shí)點(diǎn)只介紹 C++ 在 JNI 中的用法。
以下分別用 C 和 C++ 寫(xiě)了一個(gè) 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++ 的主要區(qū)別如下。
- 頭文件:C 中 string 的引用是 #include <string.h>,C++ 中 string 的引用是 #include <string>。
- extern "C":C++ 中多了 extern "C" 修飾,表示使用 C 風(fēng)格的函數(shù)鏈接,禁止 C++ 的名稱(chēng)修飾。如果不禁用名稱(chēng)修飾,C++ 編譯器會(huì)對(duì)函數(shù)名進(jìn)行修飾,以支持函數(shù)重載等特性,會(huì)導(dǎo)致 Java 無(wú)法查找到本地函數(shù)。因此,使用 extern "C" 修飾的函數(shù)不能重載(因?yàn)槊Q(chēng)不再修飾)。
- env:C 中的 env 是二級(jí)指針,所以訪問(wèn)函數(shù)通過(guò) (*env)-> 訪問(wèn);C++ 中的 env 是一級(jí)指針,所以訪問(wèn)函數(shù)通過(guò) env-> 訪問(wèn)。
2 Native 方法注冊(cè)
在 Java 中調(diào)用 Native 方法時(shí),JVM 是如何找到對(duì)應(yīng)的 JNI 方法的?這里就涉及到 Native 方法注冊(cè)問(wèn)題,JNI 提供了兩種將 Native 方法與 Java 代碼綁定的方式:靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)。它們的對(duì)比如下。官方介紹見(jiàn) → registering-native-methods。
| 特性 | 靜態(tài)注冊(cè) | 動(dòng)態(tài)注冊(cè) |
|---|---|---|
| 關(guān)聯(lián)方式 | 通過(guò)固定命名規(guī)則自動(dòng)關(guān)聯(lián) | 手動(dòng)注冊(cè)方法映射表 |
| 方法名 | 必須包含完整包名類(lèi)名 | 可自定義 |
| 效率 | 較低 (需要按名稱(chēng)查找) | 較高 (直接映射) |
| 靈活性 | 低 | 高 |
| 實(shí)現(xiàn)復(fù)雜度 | 簡(jiǎn)單 | 較復(fù)雜 |
| 適用場(chǎng)景 | 簡(jiǎn)單項(xiàng)目、少量 native 方法 | 復(fù)雜項(xiàng)目、大量 native 方法 |
2.1 靜態(tài)注冊(cè)
采用靜態(tài)注冊(cè)時(shí),本地方法名必須遵循 Java_包名_類(lèi)名_方法名 的格式。如果 Java 類(lèi)的全路徑中包含下劃線(xiàn),對(duì)應(yīng)的 JNI 方法命名中使用 "_1" 替換下劃線(xiàn),如下。官方介紹見(jiàn) → resolving-native-method-names。
// java類(lèi)全路徑
com.zhhyan8_a.Demo#nativeTest
// 對(duì)應(yīng)的JNI接口命名
Java_com_zhyan8_1a_Demo_nativeTest
2.2 動(dòng)態(tài)注冊(cè)
在 Java 中調(diào)用 System.loadLibrary("test") 時(shí),會(huì)自動(dòng)調(diào)用 JNI_OnLoad 方法,在該方法中調(diào)用 RegisterNatives 方法即可注冊(cè) Native 方法;當(dāng)包含本地庫(kù)的類(lèi)加載器(ClassLoader)被垃圾回收或 JVM 關(guān)閉時(shí),會(huì)自動(dòng)調(diào)用 JNI_OnUnload 方法,在該方法里可以釋放全局引用、關(guān)閉打開(kāi)的文件/網(wǎng)絡(luò)連接、釋放 Native 分配的內(nèi)存等操作。
static void nativeTest1(JNIEnv* env, jobject thiz)
{
// native方法實(shí)現(xiàn)
}
static jstring nativeTest2(JNIEnv* env, jobject thiz, jint i)
{
// native方法實(shí)現(xiàn)
}
static JNINativeMethod methods[] = {
{ "nativeTest1", "()V", (void*) nativeTest1 },
{ "nativeTest2", "(I)Ljava/lang/String;", (void*) nativeTest2 }
};
// 注冊(cè)動(dòng)態(tài)方法
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") 時(shí)自動(dòng)調(diào)用
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;
}
// 當(dāng)包含本地庫(kù)的類(lèi)加載器(ClassLoader)被垃圾回收、JVM關(guān)閉時(shí)自動(dòng)調(diào)用
void JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_OK) {
return;
}
// 釋放全局引用、關(guān)閉打開(kāi)的文件/網(wǎng)絡(luò)連接、釋放native分配的內(nèi)存
jclass clazz = env->FindClass("com/zhyan8/demo/Test");
if (clazz != NULL) {
(*env)->UnregisterNatives(env, clazz);
}
}
注意:JVM 不保證一定會(huì)調(diào)用 JNI_OnUnload,特別是在 JVM 異常終止時(shí);JNI_OnUnload 會(huì)在JVM 的 finalizer 線(xiàn)程中調(diào)用,需要注意線(xiàn)程安全問(wèn)題;必須通過(guò) JavaVM 指針獲取 JNIEnv,不能緩存之前獲取的 JNIEnv。
3 數(shù)據(jù)類(lèi)型
JNI 數(shù)據(jù)類(lèi)型官方介紹詳見(jiàn) → types.html。
3.1 基本數(shù)據(jù)類(lèi)型
JNI 提供以下 9 種基本數(shù)據(jù)類(lèi)型。官方介紹詳見(jiàn) → primitive-types。

可以用這些變量接收 Java 傳到 JNI 中的相關(guān)變量,如 Java 中傳遞一個(gè) int 變量到 JNI 中,JNI 函數(shù)的入?yún)⒖梢允褂?jint 變量接收。另外,這些變量又與 C++ 的變量有對(duì)應(yīng)關(guān)系,如下。
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 的位數(shù)分別是 32 位 和 64 位;C++ 中 int 和 long 的位數(shù)依賴(lài)于操作系統(tǒng),int 可能是 16 位 或 32 位,long 可能是 32 位或 64 位,long long 一般是 64 位,而 int32_t 和 int64_t 是固定的 32 位和 64 位,所以推薦使用 int32_t 轉(zhuǎn)換 jint、int64_t 轉(zhuǎn)換 jlong。
另外提供了 2 個(gè)宏常量。
#define JNI_FALSE 0
#define JNI_TRUE 1
應(yīng)用如下。
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 引用類(lèi)型
3.2.1 JNI 內(nèi)置引用類(lèi)型
JNI 提供了以下引用類(lèi)型,官方介紹詳見(jiàn) → reference-types。右邊括號(hào)中的類(lèi)是其對(duì)應(yīng)的 Java 類(lèi),如:Java 中傳遞 int[] 數(shù)組到 JNI 中,JNI 函數(shù)的入?yún)⒖梢允褂?jintArray 作為形參接收。從層級(jí)結(jié)構(gòu)中可以看到 jobject 是所有 JNI 類(lèi)的父類(lèi)。

3.2.2 局部引用、全局引用、弱全局引用
引用類(lèi)型主要分為局部引用、全局引用、弱全局引用,它們的區(qū)別如下。官方介紹詳見(jiàn) → global-and-local-references。
- 局部引用:通過(guò) NewLocalRef 函數(shù)或各種 JNI 接口創(chuàng)建(NewStringUTF、NewObject、NewCharArray、FindClass、GetObjectClass 等),會(huì)阻止 GC 回收所引用的對(duì)象,只在創(chuàng)建它的本地方法調(diào)用期間有效,不能跨線(xiàn)程使用,函數(shù)返回后局部引用所引用的對(duì)象會(huì)被 JVM 自動(dòng)釋放,或調(diào)用 DeleteLocalRef 函數(shù)釋放。
- 全局引用:通過(guò) NewGlobalRef 函數(shù)創(chuàng)建,會(huì)阻 GC 回收所引用的對(duì)象,可以跨方法、跨線(xiàn)程使用,JVM 不會(huì)自動(dòng)釋放,必須調(diào)用 DeleteGlobalRef 函數(shù)手動(dòng)釋放。
- 弱全局引用:通過(guò) NewWeakGlobalRef 函數(shù)創(chuàng)建,不會(huì)阻止 GC 回收所引用的對(duì)象,可以跨方法、跨線(xiàn)程使用,引用不會(huì)自動(dòng)釋放,在 JVM 認(rèn)為應(yīng)該回收它的時(shí)候(比如內(nèi)存緊張的時(shí)候)進(jìn)行回收而被釋放,或調(diào)用 DeleteWeakGlobalRef 函數(shù)手動(dòng)釋放,在使用弱全局引用前需要使用 IsSameObject 函數(shù)檢查對(duì)象是否已被回收。
// 局部引用接口
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)
應(yīng)用如下。
#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 中字符串官方介紹詳見(jiàn) → string-operations。JNI 提供了改進(jìn)的 UTF-8 字符串,改進(jìn)原理詳見(jiàn) → modified-utf-8-strings。
以下是 JNI 提供的常用字符串接口,jsize 是 jint 的別名。
// 新建字符串
jstring NewString(const jchar *unicode, jsize len)
jstring NewStringUTF(const char *utf)
// 獲取字符串長(zhǎng)度
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)
應(yīng)用如下。
#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
}
說(shuō)明:只有基本數(shù)據(jù)類(lèi)型可以直接打印,不能直接打印 jstring,需要將其轉(zhuǎn)換為 char * 或 char[]。
5 數(shù)組
JNI 中數(shù)組官方介紹詳見(jiàn) → array-operations。JNI 提供了以下數(shù)組類(lèi)型,右邊括號(hào)中的類(lèi)是其對(duì)應(yīng)的 Java 類(lèi),如:Java 中傳遞 int[] 數(shù)組到 JNI 中,JNI 函數(shù)的入?yún)⒖梢允褂?jintArray 作為形參接收。

以下是 JNI 提供的常用數(shù)組接口,jsize 是 jint 的別名。
// 新建數(shù)組
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)
// 獲取數(shù)組元素
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)
// 設(shè)置元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject val)
// 獲取數(shù)組長(zhǎng)度
jsize GetArrayLength(jarray array)
// 設(shè)置數(shù)組切片
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)
// 獲取數(shù)組切片
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)
// 釋放數(shù)組元素
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)
應(yīng)用如下。
#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; // 內(nèi)存分配失敗
}
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);
}
}
補(bǔ)充:通過(guò)以下方式將 jbyte、jint 等類(lèi)型轉(zhuǎn)換為 uint8_t、uint32_t 等類(lèi)型。
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 類(lèi)操作
JNI 中類(lèi)操作官方介紹詳見(jiàn) → class-operations。
以下是 JNI 提供的常用類(lèi)操作接口。
// 查找類(lèi)
jclass FindClass(const char *name)
// 獲取父類(lèi)
jclass GetSuperclass(jclass sub)
// 判斷sub是否可以安全地轉(zhuǎn)換為sup
jboolean IsAssignableFrom(jclass sub, jclass sup)
應(yīng)用如下。
#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 對(duì)象操作
JNI 中對(duì)象操作官方介紹詳見(jiàn) → object-operations。
以下是 JNI 提供的常用對(duì)象操作接口。
// 分配對(duì)象(僅分配內(nèi)存, 未調(diào)用構(gòu)造函數(shù))
jobject AllocObject(jclass clazz)
// 創(chuàng)建對(duì)象
jobject NewObject(jclass clazz, jmethodID methodID, ...)
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args)
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
// 獲取對(duì)象所屬類(lèi)
jclass GetObjectClass(jobject obj)
// 獲取對(duì)象引用類(lèi)型
jobjectRefType GetObjectRefType(jobject obj)
// 判斷對(duì)象是否是某個(gè)類(lèi)的實(shí)例
jboolean IsInstanceOf(jobject obj, jclass clazz)
// 判斷是否是同一個(gè)對(duì)象
jboolean IsSameObject(jobject obj1, jobject obj2)
jvalue 是聯(lián)合體類(lèi)型,所有元素共享一個(gè)空間,主要用于 A 方法的入?yún)ⅲㄒ?A 結(jié)尾的方法,如 NewObjectA)。官方介紹詳見(jiàn) → 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 是枚舉類(lèi)型。
typedef enum _jobjectType {
JNIInvalidRefType = 0, // 無(wú)效的引用
JNILocalRefType = 1, // 局部引用
JNIGlobalRefType = 2, // 全局引用
JNIWeakGlobalRefType = 3 // 弱全局引用
} jobjectRefType;
應(yīng)用如下。
#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 類(lèi)型簽名
JNI 在調(diào)用 Java 方法時(shí),需要通過(guò)方法簽名定位到對(duì)應(yīng)的方法。方法簽名的定義如下。官方介紹詳見(jiàn) → type-signatures。補(bǔ)充:void 對(duì)應(yīng) V。

// 對(duì)應(yīng)的方法簽名: ()V
void fun1()
// 對(duì)應(yīng)的方法簽名: (IF)D
double fun2(int a, float b)
// 對(duì)應(yīng)的方法簽名: (Ljava/lang/String;)V
void fun3(String s)
// 對(duì)應(yīng)的方法簽名: ([I)V
void fun4(int[] a)
// 對(duì)應(yīng)的方法簽名: ()[Ljava/lang/String;
String[] fun5()
// 對(duì)應(yīng)的方法簽名: (ILjava/lang/String;[I)J
long fun6(int n, String s, int[] arr)
8.1.2 獲取屬性和方法 ID
屬性和方法 ID 主要用于定位 Java 中的屬性和方法位置,為調(diào)用相應(yīng)屬性或方法做好準(zhǔn)備。它們的定義如下。官方介紹詳見(jiàn) → field-and-method-ids。
struct _jfieldID;
typedef struct _jfieldID *jfieldID;
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
以下是 JNI 提供的常用屬性和方法 ID 操作接口,官方介紹詳見(jiàn) → getfieldid、getstaticfieldid、getmethodid、getstaticmethodid。
// 獲取屬性ID
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)
// 獲取靜態(tài)屬性ID
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig)
// 獲取方法ID
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)
// 獲取靜態(tài)方法ID
jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig)
注意:無(wú)論是獲取靜態(tài)的 ID 還是非靜態(tài)的 ID, 第一個(gè)參數(shù)都是 jclass,而不是 jobject。
應(yīng)用如下。
#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 調(diào)用 Java
8.2.1 JNI 調(diào)用 Java 的屬性
以下是 JNI 提供的常用獲取和設(shè)置屬性接口,官方介紹詳見(jiàn) → gettypefield、getstatictypefield、settypefield、setstatictypefield。
// 獲取屬性
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)
// 獲取靜態(tài)屬性
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)
// 設(shè)置屬性
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)
// 設(shè)置靜態(tài)屬性
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)
應(yīng)用如下。
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");
}
}
說(shuō)明:JNI 可以獲取并修改 Java 的 private 屬性;JNI 修改 Java 的 final 屬性不會(huì)報(bào)錯(cuò),但是也不會(huì)生效。
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); // 設(shè)置屬性
jfieldID field2 = env->GetStaticFieldID(clazz, "strValue", "Ljava/lang/String;");
jstring jStrValue = (jstring) env->GetStaticObjectField(clazz, field2); // 獲取靜態(tài)屬性
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); // 設(shè)置靜態(tài)屬性
}
打印如下。
----------JNI----------
100
abc
----------Java----------
intValue=234, strValue=xyz
8.2.2 JNI 調(diào)用 Java 的方法
以下是 JNI 提供的常用調(diào)用 Java 方法的接口,官方介紹詳見(jiàn) → calltypemethod、callnonvirtualtypemethod、callstatictypemethod。
// 執(zhí)行常規(guī)的虛方法調(diào)用, 遵循Java的多態(tài)規(guī)則 (調(diào)用實(shí)例的當(dāng)前類(lèi)或其子類(lèi)中重寫(xiě)的方法)
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, ...)
// 調(diào)用特定父類(lèi)中的方法實(shí)現(xiàn) (即使子類(lèi)重寫(xiě)了該方法)
void CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
...
// 調(diào)用靜態(tài)方法
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, ...)
說(shuō)明:JNI 可以調(diào)用 Java 的 private 方法。
應(yīng)用如下。
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); // 調(diào)用test1
jmethodID method2 = env->GetMethodID(clazz, "test2", "(II)I");
jint res2 = env->CallIntMethod(work, method2, 5, 7); // 調(diào)用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); // 調(diào)用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 提供的常用異常處理接口,官方介紹詳見(jiàn) → exceptions。
// 檢查是否有掛起的異常
jboolean ExceptionCheck()
// 獲取異常對(duì)象
jthrowable ExceptionOccurred()
// 打印異常堆棧跟蹤
void ExceptionDescribe()
// 清除當(dāng)前異常
void ExceptionClear()
// 拋出新的異常
jint Throw(jthrowable obj)
// 使用指定類(lèi)拋出新異常
jint ThrowNew(jclass clazz, const char *msg)
9.1 捕獲異常
捕獲異常應(yīng)用如下。
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; // 會(huì)拋出ArithmeticException異常
}
}
如果 JNI 中不處理 Java 中拋出的異常,JNI 中程序也會(huì)繼續(xù)往下運(yùn)行,直到回到調(diào)用該 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 中可以通過(guò)以下方式處理異常。
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(); // 清除當(dāng)前異常
}
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 拋出異常
拋出異常應(yīng)用如下,java 代碼同 8.1 節(jié)。
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(); // 獲取異常對(duì)象
if (exception != NULL) {
env->ExceptionClear(); // 清除當(dāng)前異常
env->Throw(exception); // 拋出新的異常
} else {
jclass exClazz = env->FindClass("java/lang/Exception");
env->ThrowNew(exClazz, "Custom Exception"); // 使用指定類(lèi)拋出新異常
}
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 函數(shù)里 “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)
聲明:本文轉(zhuǎn)自【JNI】JNI基礎(chǔ)語(yǔ)法。