1 C 和 C++ 在 JNI 中的區(qū)別
JNI環(huán)境搭建 中介紹了在命令行和 Android Studio 中如何編譯 JNI 代碼,本文將介紹 JNI 的基礎語法,主要介紹 JNI 的數(shù)據(jù)類型、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 中的區(qū)別,然后每個知識點只介紹 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++ 的主要區(qū)別如下。
- 頭文件:C 中 string 的引用是 #include <string.h>,C++ 中 string 的引用是 #include <string>。
- extern "C":C++ 中多了 extern "C" 修飾,表示使用 C 風格的函數(shù)鏈接,禁止 C++ 的名稱修飾。如果不禁用名稱修飾,C++ 編譯器會對函數(shù)名進行修飾,以支持函數(shù)重載等特性,會導致 Java 無法查找到本地函數(shù)。因此,使用 extern "C" 修飾的函數(shù)不能重載(因為名稱不再修飾)。
- env:C 中的 env 是二級指針,所以訪問函數(shù)通過 (*env)-> 訪問;C++ 中的 env 是一級指針,所以訪問函數(shù)通過 env-> 訪問。
2 Native 方法注冊
在 Java 中調用 Native 方法時,JVM 是如何找到對應的 JNI 方法的?這里就涉及到 Native 方法注冊問題,JNI 提供了兩種將 Native 方法與 Java 代碼綁定的方式:靜態(tài)注冊和動態(tài)注冊。它們的對比如下。官方介紹見 → registering-native-methods。
| 特性 | 靜態(tài)注冊 | 動態(tài)注冊 |
|---|---|---|
| 關聯(lián)方式 | 通過固定命名規(guī)則自動關聯(lián) | 手動注冊方法映射表 |
| 方法名 | 必須包含完整包名類名 | 可自定義 |
| 效率 | 較低 (需要按名稱查找) | 較高 (直接映射) |
| 靈活性 | 低 | 高 |
| 實現(xiàn)復雜度 | 簡單 | 較復雜 |
| 適用場景 | 簡單項目、少量 native 方法 | 復雜項目、大量 native 方法 |
2.1 靜態(tài)注冊
采用靜態(tài)注冊時,本地方法名必須遵循 Java_包名_類名_方法名 的格式。如果 Java 類的全路徑中包含下劃線,對應的 JNI 方法命名中使用 "_1" 替換下劃線,如下。官方介紹見 → resolving-native-method-names。
// java類全路徑
com.zhhyan8_a.Demo#nativeTest
// 對應的JNI接口命名
Java_com_zhyan8_1a_Demo_nativeTest
2.2 動態(tài)注冊
在 Java 中調用 System.loadLibrary("test") 時,會自動調用 JNI_OnLoad 方法,在該方法中調用 RegisterNatives 方法即可注冊 Native 方法;當包含本地庫的類加載器(ClassLoader)被垃圾回收或 JVM 關閉時,會自動調用 JNI_OnUnload 方法,在該方法里可以釋放全局引用、關閉打開的文件/網(wǎng)絡連接、釋放 Native 分配的內(nèi)存等操作。
static void nativeTest1(JNIEnv* env, jobject thiz)
{
// native方法實現(xiàn)
}
static jstring nativeTest2(JNIEnv* env, jobject thiz, jint i)
{
// native方法實現(xiàn)
}
static JNINativeMethod methods[] = {
{ "nativeTest1", "()V", (void*) nativeTest1 },
{ "nativeTest2", "(I)Ljava/lang/String;", (void*) nativeTest2 }
};
// 注冊動態(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") 時自動調用
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;
}
// 釋放全局引用、關閉打開的文件/網(wǎng)絡連接、釋放native分配的內(nèi)存
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 數(shù)據(jù)類型
JNI 數(shù)據(jù)類型官方介紹詳見 → types.html。
3.1 基本數(shù)據(jù)類型
JNI 提供以下 9 種基本數(shù)據(jù)類型。官方介紹詳見 → primitive-types。

可以用這些變量接收 Java 傳到 JNI 中的相關變量,如 Java 中傳遞一個 int 變量到 JNI 中,JNI 函數(shù)的入?yún)⒖梢允褂?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 的位數(shù)分別是 32 位 和 64 位;C++ 中 int 和 long 的位數(shù)依賴于操作系統(tǒng),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 內(nèi)置引用類型
JNI 提供了以下引用類型,官方介紹詳見 → reference-types。右邊括號中的類是其對應的 Java 類,如:Java 中傳遞 int[] 數(shù)組到 JNI 中,JNI 函數(shù)的入?yún)⒖梢允褂?jintArray 作為形參接收。從層級結構中可以看到 jobject 是所有 JNI 類的父類。

3.2.2 局部引用、全局引用、弱全局引用
引用類型主要分為局部引用、全局引用、弱全局引用,它們的區(qū)別如下。官方介紹詳見 → global-and-local-references。
- 局部引用:通過 NewLocalRef 函數(shù)或各種 JNI 接口創(chuàng)建(NewStringUTF、NewObject、NewCharArray、FindClass、GetObjectClass 等),會阻止 GC 回收所引用的對象,只在創(chuàng)建它的本地方法調用期間有效,不能跨線程使用,函數(shù)返回后局部引用所引用的對象會被 JVM 自動釋放,或調用 DeleteLocalRef 函數(shù)釋放。
- 全局引用:通過 NewGlobalRef 函數(shù)創(chuàng)建,會阻 GC 回收所引用的對象,可以跨方法、跨線程使用,JVM 不會自動釋放,必須調用 DeleteGlobalRef 函數(shù)手動釋放。
- 弱全局引用:通過 NewWeakGlobalRef 函數(shù)創(chuàng)建,不會阻止 GC 回收所引用的對象,可以跨方法、跨線程使用,引用不會自動釋放,在 JVM 認為應該回收它的時候(比如內(nèi)存緊張的時候)進行回收而被釋放,或調用 DeleteWeakGlobalRef 函數(shù)手動釋放,在使用弱全局引用前需要使用 IsSameObject 函數(shù)檢查對象是否已被回收。
// 局部引用接口
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
}
說明:只有基本數(shù)據(jù)類型可以直接打印,不能直接打印 jstring,需要將其轉換為 char * 或 char[]。
5 數(shù)組
JNI 中數(shù)組官方介紹詳見 → array-operations。JNI 提供了以下數(shù)組類型,右邊括號中的類是其對應的 Java 類,如: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)
// 設置元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject val)
// 獲取數(shù)組長度
jsize GetArrayLength(jarray array)
// 設置數(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)
應用如下。
#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);
}
}
補充:通過以下方式將 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 提供的常用對象操作接口。
// 分配對象(僅分配內(nèi)存, 未調用構造函數(shù))
jobject AllocObject(jclass clazz)
// 創(chuàng)建對象
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 是聯(lián)合體類型,所有元素共享一個空間,主要用于 A 方法的入?yún)ⅲㄒ?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 操作接口,官方介紹詳見 → 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)
注意:無論是獲取靜態(tài)的 ID 還是非靜態(tài)的 ID, 第一個參數(shù)都是 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 提供的常用獲取和設置屬性接口,官方介紹詳見 → 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)
// 設置屬性
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)
// 設置靜態(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)
應用如下。
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); // 獲取靜態(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); // 設置靜態(tài)屬性
}
打印如下。
----------JNI----------
100
abc
----------Java----------
intValue=234, strValue=xyz
8.2.2 JNI 調用 Java 的方法
以下是 JNI 提供的常用調用 Java 方法的接口,官方介紹詳見 → calltypemethod、callnonvirtualtypemethod、callstatictypemethod。
// 執(zhí)行常規(guī)的虛方法調用, 遵循Java的多態(tài)規(guī)則 (調用實例的當前類或其子類中重寫的方法)
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, ...)
// 調用特定父類中的方法實現(xiàn) (即使子類重寫了該方法)
void CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
...
// 調用靜態(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, ...)
說明: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 中程序也會繼續(xù)往下運行,直到回到調用該 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 節(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(); // 獲取異常對象
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 函數(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)
聲明:本文轉自【JNI】JNI基礎語法。