1. 程式人生 > >FFmpeg In Android - JNI文件

FFmpeg In Android - JNI文件

NDK官方文件_詳解
NDK官方示例_github

轉:Android NDK開發掃盲及最新CMake的編譯使用
轉:JNI/NDK開發指南

文章目錄

The Java™ Native Interface

  • Programmer’s Guide and Specification

1 Introduction

JavaTM Native Interface(JNI)是Java平臺一項強力的特性.使用JNI的應用既可以使用Java語言,也可以使用C和C++語言。JNI允許程式設計師在不需要廢棄現有程式碼的情況下,就可以使用Java平臺的能力。由於JNI是Java平臺的一部分,所以程式設計師可以一次性解決互操作性問題,並期望他們的解決方案能夠在所有的Java平臺都是有效的。
 本書既是JNI的程式設計指南,也是參考手冊。本書包含了三個部分:

  • 第二章通過一個簡單的示例介紹了JNI,主要是面向JNI的初學者。
  • 第三章到第十章概述了很多JNI的特性,組成了程式設計師的指南。我們也會通過一系列短小但能描述清楚JNI特性的示例,這些都是JNI程式設計中很有用的技術。
  • 第十一到第十三章給出了所有JNI型別和方法的明確規範,這幾章也可以作為參考手冊。
    本書力圖滿足不同使用者使用JNI時的需求,此教程和程式設計指南面向初學者,不過有經驗的開發者也可以作為參考手冊。Blablabla…
    本書假設你同時具備了Java,C,C++基本的開發知識,如果還沒有,可以參考其他書籍先行學習。接下來介紹JNI的背景,角色和演化。

1.1 The Java Platform and Host Environment

因為所寫的程式包括了Java,C,C++語言,讓我們先澄清這些語言在程式設計環境中的作用範圍。Java平臺是由Java虛擬機器(Java VM)和Java API組成的程式設計環境,Java程式用Java語言編寫,並編譯成機器無關的二進位制類,一個類可以在任何Java VM執行。任何Java平臺的實現都保證了支援Java語言,VM和API。
術語本地環境是指主機作業系統,本地庫的集合,和機器指令集合。本地應用程式是由本地語言如C/C++編寫的,編譯成主機相關的二進位制程式碼,然後用本地庫連結。例如,一個為特定主機編譯的C語言程式,很大可能無法在另外的作業系統上執行。
Java平臺一般是部署在本地環境的上層,例如,the Java Runtime Environment(JRE)是一項由Sun公司開發,可以在Solaris和Windows等作業系統上支援Java平臺的產品。Java平臺提供了一系列跟本地環境無關的特性給應用程式使用。

1.2 Role of the JNI

當Java平臺部署在本地環境之上時,允許Java應用程式與用其他語言編寫的本機程式碼緊密合作可能是可取的或必要的,程式設計師已經開始採用Java平臺來構建傳統上用C和C++編寫的應用程式。由於對遺留程式碼的現有投資,Java應用程式將在未來許多年與C和C++程式碼共存。
JNI是一種功能強大的特性,它允許您利用Java平臺,但仍然使用其他語言編寫的程式碼。作為Java虛擬機器實現的一部分,JNI是一個雙向介面,允許Java應用程式呼叫本機程式碼,反之亦然。下圖描繪了JNI扮演的角色:

JNI是設計來處理需要Java程式碼與原生代碼一起編譯的情況,作為一個雙向介面,JNI支援兩種型別的原生代碼:本地庫和本地應用程式。

  • 你可以用JNI寫Java程式可以呼叫的本地方法,來呼叫本地庫實現的函式,Java程式就像呼叫普通方法一樣呼叫本地方法,但實際上本地方法是由另一種語言實現的,並駐留在本地庫。
  • JNI支援invocation interface,允許你嵌入一個Java VM到本地程式。本地程式可以連結一個實現了Java VM的本地庫,然後使用invocation interface來執行由Java語言編寫的軟體元件。例如,一個C語言編寫的web瀏覽器可以通過這種嵌入式Java虛擬機器執行下載Applet。

1.3 Implications of Using the JNI

記住,一旦使用了JNI,就面臨著失去兩項Java平臺才有益處。第一,不再很容易地在各種系統上執行,即使Java層程式碼編寫為可移植的,仍需要保證原生代碼也是可移植的。第二,Java語言是型別安全的,本地語言C/C++不是,你使用JNI時需要格外小心,一段行為不當的原生代碼可能會導致整個應用程式崩潰。因此,Java應用程式在呼叫JNI功能之前需要進行安全檢查。一個通用的規則是,仔細地設計程式,原生代碼越少越好,兩者需要清晰地隔離。

1.4 When to Use the JNI

在您使用JNI開始專案之前,值得花費時間來調查是否有更合適的替代解決方案。如上節提到的,相比純Java程式,JNI程式有著固有的缺點,例如失去了Java語言保證的型別安全特性。也有許多允許Java應用程式與用其他語言編寫的程式碼進行互動的替代方法, 例如:

  • 通過TCP/IP連線,或者通過IPC程序間通訊
  • 通過JDBC API
  • 通過Java IDL API
    這些解決方案的相同點是Java程式和本地程式隔離在不同的程序(甚至在不同的機器),程序隔離有一個很大的益處,就是程序空間的保護措施令錯誤隔離了:本地程序的崩潰不會影響通過TCP/IP通訊的Java程序。
    然而,有時Java程式碼跟原生代碼的確需要在同一個程序,這就是JNI起作用的地方了,例如下面的情景:
  • 一個應用需要用到某個主機相關的特性,Java語言是不支援的。比如想呼叫某些檔案操作,Java不支援,如果通過程序通訊又太笨重和低效率了
  • 與原生代碼互動,如果通過程序通訊的方式,資料複製傳輸的成本太高,在同一程序則效率大大提高
  • 你可能希望把一小部分對時間要求較高的程式碼用本地語言實現,例如彙編。如果一個3D密集型程式花了大部分時間在圖形渲染上,可能需要用匯編來寫圖形庫的關鍵程式碼以達到最高效能。
    總之,如果你的Java程式碼必須和原生代碼在同一程序,就使用JNI吧。

1.5 Evolution of the JNI

The need for Java applications to interoperate with native code has been recognized since the very early days of the Java platform. The first release of the Java platform, Java Development Kit (JDK™) release 1.0, included a native method interface that allowed Java applications to call functions written in other languages such as C and C++. Many thirdparty applications, as well as the implementation of the Java class libraries (including, for example, java.lang , java.io , and java.net ), relied on the native method interface to access the features in the underlying host environment.
Unfortunately, the native method interface in JDK release 1.0 had two major problems:

  • First, the native code accesses fields in objects as members of C structures.
    However, the Java virtual machine specification does not define how objects are laid out in memory. If a given Java virtual machine implementation lays out objects in a way other than that assumed by the native method interface, then you have to recompile the native method libraries.
  • Second, the native method interface in JDK release 1.0 relies on a conserva-
    tive garbage collector because native methods can get hold of direct pointers to objects in the virtual machine. Any virtual machine implementation that uses more advanced garbage collection algorithms cannot support the native method interface in JDK release 1.0.
    The JNI was designed to overcome these problems. It is an interface that can be supported by all Java virtual machine implementations on a wide variety of host environments. With the JNI:
  • Each virtual machine implementor can support a larger body of native code.
  • Development tool vendors do not have to deal with different kinds of native method interfaces.
  • Most importantly, application programmers are able to write one version of their native code and this version will run on different implementations of the Java virtual machine.
    The JNI was first supported in JDK release 1.1. Internally, however, JDK
    release 1.1 still uses old-style native methods (as in JDK release 1.0) to implement the Java APIs. This is no longer the case in Java 2 SDK release 1.2 (formerly known as JDK release 1.2). Native methods have been rewritten so that they conform to the JNI standard.
    The JNI is the native interface supported by all Java virtual machine implementations. From JDK release 1.1 on, you should program to the JNI. The old-style native method interface is still supported in Java 2 SDK release 1.2, but will not (and cannot) be supported in advanced Java virtual machine implementations in the future.
    Java 2 SDK release 1.2 contains a number of JNI enhancements. The
    enhancements are backward compatible. All future evolutions of JNI will maintain complete binary compatibility.

1.6 Example Programs

This book contains numerous example programs that demonstrate JNI features. The example programs typically consist of multiple code segments written in the Java programming language as well as C or C++ native code. Sometimes the native code refers to host-specific features in Solaris and Win32. We also show how to build JNI programs using the command line tools (such as javah ) shipped with JDK and Java 2 SDK releases.
Keep in mind that the use of the JNI is not limited to specific host environments or specific application development tools. The book focuses on writing the code, not on the tools used to build and run the code. The command line tools bundled with JDK and Java 2 SDK releases are rather primitive. Third-party tools
may offer an improved way to build applications that use the JNI. We encourage you to consult the JNI-related documentation bundled with the development tools of your choice.
You can download the source code of the examples in this book, as well as the latest updates to this book, from the following web address:
jni books online

2 Getting Started

本節寫一個使用JNI的簡單例子,使用Java語言呼叫C語言函式來列印"Hello world!".(省略了很多翻譯)

例子

package com.example.king.cmakedemo;
public class NativeUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public native void stringFromJNI();
}
#include <jni.h>
#include <string>
#include <android/log.h>

#define  ANDROID_LOG_TAG    "native"
#define  ANDROID_LOG_ERR    "native"

#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,ANDROID_LOG_TAG,__VA_ARGS__)

#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,ANDROID_LOG_TAG,__VA_ARGS__)

#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,ANDROID_LOG_ERR,__VA_ARGS__)

extern "C" JNIEXPORT void JNICALL
Java_com_example_king_cmakedemo_NativeUtil_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    const char * hello = "Hello from C++";
    LOGD(hello);
}

3 Basic Types, Strings, and Arrays

3.1 Method declaration and Arguments

開發者問的最多問題是Java應用和原生代碼互動時,資料型別是怎樣從Java語言對應到本地C/C++等語言的。上一節的簡單例子,我們沒有傳遞引數,也沒有返回值,本地函式只是簡單地列印了一句話。
不過在實踐中,大多數程式傳遞引數到原生代碼,並接收返回值,本節會表明怎樣做到這些,先從基本型別int和普通物件String,array等開始.下一節再討論任意物件的處理方式,包括原生代碼怎樣訪問Java物件的成員和方法.
如上面看到宣告函式的方式,JNIEXPORTJNICALL巨集(在jni.h中定義)確保此函式是從本地庫匯出,並且C編譯器生成可以正確呼叫此函式的程式碼.格式是以Java_開頭,然後到類名,再到方法名.第一個引數JNIEnv,是一個結構體指標,該結構體內部包含了函式指標,每個函式指標都指向一個JNI函式.原生代碼總是通過JNI函式來訪問Java VM裡面的資料結構.下圖描繪了JNIEnv:

/**jni.h中的宣告,可以看出實際C和C++最後都是呼叫了JNINativeInterface */
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNINativeInterface {
  jint        (*GetVersion)(JNIEnv *);
  ...省略...
};

struct _JNIEnv {
    const struct JNINativeInterface* functions;
  #if defined(__cplusplus)
    jint GetVersion()
    { return functions->GetVersion(this); }
    ...省略...
   #endif /*__cplusplus*/
};

第二個引數的含義取決於是成員方法還是靜態方法,如果是成員方法,就是jobject代表呼叫該方法的當前物件的引用,如果是靜態方法,就是jclass代表定義了此方法的這個class.

3.2 Mapping of Types

JNI函式宣告中的引數型別,在本地程式語言中都有對應的型別.JNI定義了一系列C/C++語言和Java語言相對應的型別.Java語言有兩種資料型別:基本型別int,float,char等等和引用型別classes,instances,arrays(strings是java.lang.String的例項).
JNI用不同的方式處理這兩種型別:基本型別直接對映到本地,例如int直接對應jint(typedef int jint;), float對應jfloat(typedef float jfloat;),如下:

/**jni.h*/
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */

引用型別對於原生代碼來說是不透明的,引用型別是指向Java VM內部資料結構的指標,該資料結構的實際記憶體佈局對開發者是不可見的,原生代碼必須通過JNI函式即JNIEnv來操作這些引用型別.例如java.lang.String對應的jstring,原生代碼是不知道其內容是什麼,只能呼叫GetStringUTFChars等函式來獲取其內容.
所有的引用型別都是jobject型別,為了便利性和加強型別安全,JNI定義了一系列引用型別的子型別.(A是B的子型別,則每個A的例項都是B的例項),這些子型別都對應了平時Java開發中的常用引用型別。例如jstring表示字串String,jobjectArray表示object陣列,如下圖示:

3.2 Accessing Strings

JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
/* ERROR: incorrect use of jstring as a char* pointer */
printf("%s", prompt);
...
}

3.2.1 Converting to Native Strings

原生代碼必須使用JNI函式來轉換jstring到C/C++字串,JNI支援從Unicode到UTF-8的相互轉換,Unicode用16bit來表示字元,UTF-8用變長編碼,其可以相容7bit的ASCII編碼.UTF-8類似C字串以’\0’結束,即使裡面包含了非ASCII編碼字元.所有ASCII字元在UTF-8中的編碼值跟原來一樣.一個位元組的最高位設定表示16位Unicode多位元組編碼的開始.

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_king_cmakedemo_NativeUtil_getLine(
        JNIEnv* env,
        jobject thiz, jstring prompt) {
        /* ERROR: incorrect use of jstring as a char* pointer */
		//printf("%s", prompt);
		
    const char* str_prompt = env->GetStringUTFChars(prompt, NULL);
    if (str_prompt == NULL) {
    	return NULL;
    }
    LOGD(str_prompt);
    env->ReleaseStringUTFChars(prompt, str_prompt);

    const char * hello = "Hello from C++";
    return env->NewStringUTF(hello);
}

不要忘記檢查GetStringUTFChars的返回值,Java VM需要分配記憶體來存放UTF-8字串,有小概率記憶體分配會失敗,這種情況下會返回NULL並且丟擲OutOfMemoryError異常,在JNI丟擲異常不同於在Java中丟擲異常,在JNI丟擲異常不會打斷正在執行的控制流程,因此,我們需要顯式地用return來跳過剩餘程式碼流程並返回,返回後,在上層Java呼叫處會丟擲異常.

3.2.2 Freeing Native String Resources

When your native code finishes using the UTF-8 string obtained through GetStringUTFChars, it calls ReleaseStringUTFChars. Calling ReleaseStringUTFChars indicates that the native method no longer needs the UTF-8 string returned by GetStringUTFChars; thus the memory taken by the UTF-8 string can be freed. Failure to call ReleaseStringUTFChars would result in a memory leak, which could ultimately lead to memory exhaustion.

3.2.3 Constructing New Strings

可以原生代碼通過JNI函式NewStringUTF來建立java.lang.String的物件,NewStringUTF函式接收C語言的UTF-8字串然後構造java.lang.String物件,新構建的Unicode字串物件表示了跟UTF-8相同的序列.如果記憶體分配失敗導致建立物件失敗,將丟擲OutOfMemoryError異常並返回NULL.本示例不需要檢查,直接丟擲異常到上層.

3.2.4 Other JNI String Functions

/**
* 說明:以 UTF-16 的編碼方式建立一個 Java 的字串(jchar 的定義為 uint16_t)
@Param unicodeChars:指向字元陣列的指標
@Param len:字元陣列的長度
*/
jstring NewString(const jchar* unicodeChars, jsize len)

/**
* 按Unicode格式來獲取字串長度
*/
jsize GetStringLength(jstring string)

/**
* 按UTF-8格式來獲取字串長度(也可以轉換jstring後用C函式strlen獲取)
*/
jsize GetStringUTFLength(jstring string)

/**
* 獲取Unicode編碼的字串(本地環境支援Unicode時很有用)
*/
const jchar* GetStringChars(jstring string, jboolean* isCopy)

/**
* 釋放指定的字串指標,通常來說,Get 和 Release 是成對出現的
* @Param string: Java 風格的字串
* @Param chars/utf: 對應的 C 風格的字串 
*/
void ReleaseStringChars(jstring string, const jchar* chars)
void ReleaseStringUTFChars(jstring string, const char* utf)

void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)
void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf)
const jchar* GetStringCritical(jstring string, jboolean* isCopy)
void ReleaseStringCritical(jstring string, const jchar* carray)
其他函式待續...

上面的引數jboolean* isCopy,當獲取的字串是原來的一份副本時,它的值會被設為JNI_TRUE,當獲取的字串是直接指向原內容的指標時,它的值會被設為JNI_FALSE.當返回JNI_FALSE時,原生代碼禁止修改字串內容,否則原內容也會被修改,這違反了Java平臺String例項是不可變的規範.大多數情況下,該引數傳0即可.

3.3 Accessing Arrays

int[] iarr;
float[] farr;
Object[] oarr;
int[][] arr2;

iarr, farr是primitive(基本型別)陣列,oarr, arr2是object(引用型別)陣列.在原生代碼訪問陣列內容,也類似與訪問字串,需要用JNI函式.

/** 訪問陣列,錯誤程式碼 */
extern "C" JNIEXPORT jint JNICALL
Java_com_example_king_cmakedemo_NativeUtil_sumArray(
        JNIEnv* env,
        jobject /* this */,
        jintArray arr) {
    int i, sum = 0;
    for (i = 0; i < 10; i++) {
        sum += arr[i];
    }
    return sum;
}

/** 應該這樣訪問陣列 */
extern "C" JNIEXPORT jint JNICALL
Java_com_example_king_cmakedemo_NativeUtil_sumArray(
        JNIEnv* env,
        jobject /* this */,
        jintArray arr) {
    jint buf[3];
    jint i, sum = 0;
    env->GetIntArrayRegion(arr, 0, 3, buf);
    for (i = 0; i < 3; i++) {
        sum += buf[i];
    }
    return sum;
}

/** 或者這樣訪問陣列 */
extern "C" JNIEXPORT jint JNICALL
Java_com_example_king_cmakedemo_NativeUtil_sumArray(
        JNIEnv* env,
        jobject /* this */,
        jintArray arr) {
    jint * carr;
    jint i, sum = 0;
    carr = env->GetIntArrayElements(arr, NULL);
    if (carr == NULL) {
        return 0; /* exception occurred */
    }
    for (i=0; i<3; i++) {
        sum += carr[i];
    }
    env->ReleaseIntArrayElements(arr, carr, 0);
    return sum;
}

//複製primitive陣列
Get<Type>ArrayRegion (GetIntArrayRegion(), GetFloatArrayRegion(), GetBooleanArrayRegion() ...)
//設定primitive陣列的資料
Set<Type>ArrayRegion

//獲取primitive陣列的指標(有可能是返回其副本的指標)
Get<Type>ArrayElements
Release<Type>ArrayElements

//獲取陣列的長度
jsize GetArrayLength(jarray array)
// 建立一個給定長度的primitive陣列
New<Type>Array

GetPrimitiveArrayCritical
ReleasePrimitiveArrayCritical

3.3.1 Accessing Arrays of Objects

JNI提供了獨立的函式來訪問object陣列,GetObjectArrayElement返回某個位置的元素,SetObjectArrayElement設定某個位置的資料,不像primitive陣列,不能一次得到所有的陣列內容.

/** 返回一個新建立的二維陣列 */
extern "C" JNIEXPORT jobjectArray JNICALL
Java_com_example_king_cmakedemo_NativeUtil_initInt2DArray(
        JNIEnv* env,
        jobject /* this */,
        jint size) {

    jobjectArray result;
    int i;
    jclass intArrCls = env->FindClass("[I");
    if (intArrCls == NULL) {
        return NULL; /* exception thrown */
    }
    result = env->NewObjectArray(size, intArrCls,
                                    NULL);
    if (result == NULL) {
        return NULL; /* out of memory error thrown */
    }
    for (i = 0; i < size; i++) {
        jint tmp[size]; /* make sure it is large enough! */
        int j;
        jintArray iarr = env->NewIntArray(size);
        if (iarr == NULL) {
            return NULL; /* out of memory error thrown */
        }
        for (j = 0; j < size; j++) {
            tmp[j] = i + j;
        }
        env->SetIntArrayRegion(iarr, 0, size, tmp);
        env->SetObjectArrayElement(result, i, iarr);
        env->DeleteLocalRef(iarr);
    }
    return result;
}

4 Fields and Methods

現在你知道怎樣用JNI來訪問基本型別和引用型別比如string,array了,下一步就是與任意物件的成員和方法互動,除此之外,也包括怎樣從原生代碼呼叫Java語言的方法,一般稱為回撥.
先介紹可以支援訪問成員和方法的JNI函式,稍後介紹怎樣令這些操作效率更高的快取技術,In the last
section, we will discuss the performance characteristics of calling native methods as well as accessing fields and calling methods from native code.

4.1 Accessing Fields

Java語言支援兩種fields,每個類物件都有自己的instance fields的副本,所有的類物件都共享static fields,JNI提供了讓原生代碼可以獲取和設定物件instance fields,static fields的函式.

/** Instance Fields Access */
public class NativeUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public String s = "abc";

    public native String stringFromJNI();

    public native void accessField();

}

extern "C" JNIEXPORT void JNICALL
Java_com_king_cmakedemo_NativeUtil_accessField(
        JNIEnv* env,
        jobject obj) {

    jfieldID fid; /* store the field ID */
    jstring jstr;
    const char *str;
    /* Get a reference to obj’s class */
    jclass cls = env->GetObjectClass(obj);

    /* Look for the instance field s in cls */
    fid = env->GetFieldID(cls, "s",
                             "Ljava/lang/String;");
    if (fid == NULL) {
        return; /* failed to find the field */
    }

    /* Read the instance field s */
    jstr = static_cast<jstring>(env->GetObjectField(obj, fid));
    str = env->GetStringUTFChars(jstr, NULL);
    if (str == NULL) {
        return; /* out of memory */
    }
    LOGD(" c.s = \"%s\"\n", str);
    env->ReleaseStringUTFChars(jstr, str);

    /* Create a new string and overwrite the instance field */
    jstr = env->NewStringUTF("123");
    if (jstr == NULL) {
        return; /* out of memory */
    }
    env->SetObjectField(obj, fid, jstr);
}

4.1.1 Field Descriptors

4.1.2 Accessing Static Fields

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

NativeType Get<type>Field(jobject obj, jfieldID fieldID)  例如:  GetIntField, GetObjectField
NativeType GetStaticField(jobject obj, jfieldID fieldID) 例如: GetStaticIntField , GetStaticObjectField

void Set<type>Field(jobject obj, jfieldID fieldID, NativeType value)   例如: SetIntField , SetObjectField

void SetStaticField(jobject obj, jfieldID fieldID, NativeType value) 例如:SetStaticIntField, SetStaticObjectField

4.2 Calling Methods

Java語言有幾種方法,成員方法,靜態方法,構造方法。

package com.king.cmakedemo;
public class Util {
    public void callback() {
        System.out.println("Util callback");
    }
}

package com.king.cmakedemo;
public class NativeUtil {
    static {
        System.loadLibrary("native-lib");
    }
    
    public native void nativeMethod();
    
    @Override
    public void callback() {
        System.out.println("NativeUtil callback");
    }

    public static void callback2() {
         System.out.println("static callback");
    }

    public native String stringFromJNI();
}

jstring MyNewString(JNIEnv * env, const jchar* chars, jint len);

/**
* 呼叫成員方法,  呼叫靜態方法,呼叫父類方法
*/
extern "C" JNIEXPORT void JNICALL
Java_com_king_cmakedemo_NativeUtil_nativeMethod(
        JNIEnv* env,
        jobject obj) {
    jjclass cls = env->GetObjectClass(obj);

    jmethodID mid =
            env->GetMethodID(cls, "callback", "()V");
    if (mid == NULL) {
        return; /* method not found */
    }
    // 呼叫成員方法,
    env->CallVoidMethod(obj, mid);

    mid = env->GetStaticMethodID(cls, "callback2", "()V");
    if (mid == NULL) {
        return; /* method not found */
    }
    //  呼叫靜態方法
    env->CallStaticVoidMethod(cls, mid);

   // 呼叫父類方法
    cls = env->GetSuperclass(cls);
    mid = env->GetMethodID(cls, "callback", "()V");
    env->CallNonvirtualVoidMethod(obj, cls, mid);

  // 呼叫String類構造方法
  const char content[] = "HelloNewString";
  jstring str_content = env->NewStringUTF(content);
  const jchar* jchar_content = env->GetStringChars(str_content, 0);

  jstring s = MyNewString(env, jchar_content, env->GetStringLength(str_content));

  LOGD("new create string=%s\n", env->GetStringUTFChars(s, 0));
}

// 呼叫String類構造方法
jstring MyNewString(JNIEnv * env, const jchar* chars, jint len) {
    jclass stringClass;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    stringClass = env->FindClass("java/lang/String");
    if (stringClass == NULL) {
        return NULL; /* exception thrown */
    }

    /* Get the method ID for the String(char[]) constructor */
    cid = env->GetMethodID(stringClass,
                              "<init>", "([C)V");
    if (cid == NULL) {
        return NULL; /* exception thrown */
    }

    /* Create a char[] that holds the string characters */
    elemArr = env->NewCharArray(len);
    if (elemArr == NULL) {
        return NULL; /* exception thrown */
    }
    env->SetCharArrayRegion(elemArr, 0, len, chars);
    /* Construct a java.lang.String object */
    result = static_cast<jstring>(env->NewObject(stringClass, cid, elemArr));

    /* Free local references */
    env->DeleteLocalRef(elemArr);
    env->DeleteLocalRef(stringClass);
    return result;
}	

4.3 Caching Field and Method IDs

獲取成員和方法的ID需要基於成員和方法的名稱和描述符進行符號查詢,這一過程較耗資源,本節介紹可以提高效率的技術。思路是獲取到成員和方法ID後,把它們快取起來,供後面可以重複使用。有兩種快取的方式,取決於是使用的時候才快取,還是類靜態初始化時就快取起來。

4.3.1 Caching at the Point of Use

...省略程式碼...
extern "C" JNIEXPORT void JNICALL
Java_com_king_cmakedemo_NativeUtil_accessField(
        JNIEnv* env,
        jobject obj) {
        
	static jfieldID fid = NULL;
	if (fid == NULL) {
	        fid = env->GetFieldID(cls, "s",
	                          "Ljava/lang/String;");
	 }
 }
...省略程式碼...

4.3.2 Caching in the Defining Class’s Initializer

public class NativeUtil extends Util {
    static {
        System.loadLibrary("native-lib");
        initIDs();
    }
    public native String nativeMethod();

    @Override
    public void callback() {
        Log.e("kkunion", "NativeUtil callback");
    }

    public native String stringFromJNI();

    public native void accessField();

    private static native void initIDs();
}

jmethodID MID_InstanceMethodCall_callback;

extern "C" JNIEXPORT void JNICALL
Java_com_king_cmakedemo_NativeUtil_initIDs(
        JNIEnv* env,
        jclass cls) {
    MID_InstanceMethodCall_callback = env->GetMethodID(cls, "callback", "()V");
}

4.3.3 Comparison between the Two Approaches to Caching IDs

Caching IDs at the point of use is the reasonable solution if the JNI programmer does not have control over the source of the class that defines the field or method. For example, in the MyNewString example, we cannot inject a custom initIDs native method into the java.lang.String class in order to precompute and cache the method ID for the java.lang.String constructor. Caching at the point of use has a number of disadvantages when compared with caching in the static initializer of the defining class.
• As explained before, caching at the point of use requires a check in the execution fast path and may also require duplicated checks and initialization of the same field or method ID.
• Method and field IDs are only valid until the class is unloaded. If you cache field and method IDs at the point of use you must make sure that the defining class will not be unloaded and reloaded as long as the native code still relies on the value of the cached ID. (The next chapter will show how you can keep a class from being unloaded by creating a reference to that class using the JNI.) On the other hand, if caching is done in the static initializer of the defining class, the cached IDs will automatically be recalculated when the class is unloaded and later reloaded. Thus, where feasible, it is preferable to cache field and method IDs in the static initializer of their defining classes.

4.4 Performance of JNI Field and Method Operations

After learning how to cache field and method IDs to enhance performance, you might wonder: What are the performance characteristics of accessing fields and calling methods using the JNI? How does the cost of performing a callback from
native code (a native/Java callback) compare with the cost of calling a native method (a Java/native call), and with the cost of calling a regular method (a Java/Java call)?
The answer to this question no doubt depends on how efficiently the underlying virtual machine implements the JNI. It is thus impossible to give an exact account of performance characteristics that is guaranteed to apply to a wide variety of virtual machine implementations. Instead, we will analyze the inherent cost of native method calls and JNI field and method operations and provide a general performance guideline for JNI programmers and implementors.
Let us start by comparing the cost of Java/native calls with the cost of Java/Java calls. Java/native calls are potentially slower than Java/Java calls for the following reasons:

  • Native methods most likely follow a different calling convention than that used by Java/Java calls inside the Java virtual machine implementation. As a result, the virtual machine must perform additional operations to build arguments and set up the stack frame before jumping to a native method entry point.
  • It is common for the virtual machine to inline method calls. Inlining Java/native calls is a lot harder than inlining Java/Java calls.
    We estimate that a typical virtual machine may execute a Java/native call roughly two to three times slower than it executes a Java/Java call. Because a Java/Java call takes just a few cycles, the added overhead will be negligible unless the native method performs trivial operations. It is also possible to build virtual machine implementations with Java/native call performance close or equal to that of Java/Java calls. (Such virtual machine implementations, for example, may adopt the JNI calling convention as the internal Java/Java calling convention.)
    The performance characteristics of a native/Java callback is technically similar to a Java/native call. In theory, the overhead of native/Java callbacks could also be within two to three times of Java/Java calls. In practice, however, native/Java callbacks are relatively infrequent. Virtual machine implementations do not usually optimize the performance of callbacks. At the time of this writing many production virtual machine implementations are such that the overhead of a native/Java callback can be as much as ten times higher than a Java/Java call.

5 Local and Global References

JNI將類例項和陣列型別(jobject, jclass, jstring, jarray等)當成不透明引用,原生代碼從不直接檢視這些引用的內容,而是用JNI函式來獲取內容,你只需要關心JNI中的各種引用型別即可:

  • JNI支援三種不透明引用:區域性引用local references, 全域性引用global references, and 弱全域性引用weak global references.
  • 區域性引用, 全域性引用有不同的生命週期,區域性引用會自動釋放,而全域性引用,弱全域性引用需要手動釋放。
  • 區域性引用, 全域性引用不會被垃圾收集器收集,而弱全域性引用會。
  • 不是所有的引用都可以在其他上下文,例如,區域性引用在某個程式碼塊被建立,退出該程式碼塊作用域後就不能使用了。
    本章將討論這些細節,適當地管理這些引用對程式碼的穩定和高效能是至關重要的。

5.1 Local and Global References

什麼是區域性引用,全域性引用,它們有什麼不同?下面將用一系列例子來說明。

5.1.1 Local References

大多數JNI函式會建立區域性引用,涉及到所有jobject的子類,如jclass, jstring, and jarray等。一個區域性引用只在本地函式內建立它時才可用,並且只能該函式內使用。所有在本地函式內建立的區域性引用都會在該函式返回後釋放(即使被引用的物件還一直存在,該區域性引用也不可以繼續使用了)。
不能儲存區域性引用在static變數中,並希望在後續呼叫中繼續使用該區域性引用。例如下面的例子是錯誤的:

/* This code is illegal */
jstring MyNewString(JNIEnv *env, jchar *chars, jint len)
{
 	static jclass stringClass = NULL;
	jmethodID cid;
	jcharArray elemArr;
	jstring result;
	if (stringClass == NULL) {
		stringClass = (*env)->FindClass(env,
		"java/lang/String");
		if (stringClass == NULL) {
			return NULL; /* exception thrown */
		}
	}
	/* It is wrong to use the cached stringClass here, because it may be invalid. */
	cid = env->GetMethodID(stringClass, "<init>", "([C)V");
	...
	elemArr = env->NewCharArray(len);
	...
	result = env->NewObject(stringClass, cid, elemArr);
	env->DeleteLocalRef(elemArr);
	return result;
}

這個函式結束後,stringClass變數儲存的區域性引用也會被釋放,下次再呼叫這個函式嘗試使用stringClass變數裡的區域性引用時會出現記憶體錯誤或崩潰了。
有兩種方式另區域性引用失效,一種是上面提到的,本地方法返回後區域性引用自動釋放,另一種是顯式呼叫DeleteLocalRef。為什麼需要顯式地呼叫呢?區域性引用在失效前會阻止垃圾收集器回收其引用的物件,顯式呼叫後,可以立即釋放。
區域性引用不能線上程間共享,只能在建立它的那個執行緒中使用。

5.1.2 Global References

全域性引用可以在多個函式間共用,也可以在多個執行緒間共用,直到顯式釋放。跟區域性引用一樣,全域性引用失效前也會阻止垃圾收集器回收其引用的物件,但跟區域性引用不同的是,很多JNI函式會建立區域性引用,而全域性引用只能通過NewGlobalRef來建立。

jstring MyNewString(JNIEnv * env, const jchar* chars, jint len) {
    static jclass stringClass = NULL;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    if (stringClass == NULL) {
        jclass localRecCls = env->FindClass("java/lang/String");
        if (localRecCls == NULL) {
            return NULL;
        }
        stringClass = static_cast<jclass>(env->NewGlobalRef(localRecCls));

        env->DeleteLocalRef(localRecCls);
        if (stringClass == NULL) {
            return NULL;/* exception thrown */
        }
    }

	......
}

5.1.3 Weak Global References

用NewGlobalWeakRef建立弱全域性引用,用DeleteGlobalWeakRef釋放。跟全域性引用一樣,弱全域性引用可以在多個函式間共用,也可以在多個執行緒間共用;跟全域性引用不同的是,弱全域性引用不會阻止垃圾收集器回收其引用的物件。

jstring MyNewString(JNIEnv * env, const jchar* chars, jint len) {
    static jclass stringClass = NULL;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    if (stringClass == NULL) {
        jclass localRecCls = env->FindClass("java/lang/String");
        if (localRecCls == NULL) {
            return NULL;
        }
        stringClass = static_cast<jclass>(env->NewWeakGlobalRef	(localRecCls));

        env->DeleteLocalRef(localRecCls);
        if (stringClass == NULL) {
            return NULL;/* exception thrown */
        }
    }

	......
}

5.1.4 Comparing References

給定兩個引用,可以檢查是否指向同一個物件:
jboolean IsSameObject(jobject ref1, jobject ref2)
返回JNI_TRUE( 或者1 )表明是指向同一個,相反JNI_FALSE( 或者0 )。通過這個函式,可以檢查引用是否指向空物件
jboolean ret = env->IsSameObject(ref, NULL);
或者
ref == NULL
IsSameObject對弱全域性引用有特殊的作用,可以用來檢查弱全域性引用是否仍指向一個有效的活動物件,假設wobj是一個弱全域性引用,
env->IsSameObject(wobj, NULL);
返回JNI_TRUE表明其引用的物件已經被回收,返回JNI_FALSE表明其仍然引用著一個有效的活動物件。

5.2 Freeing References

JNI的每個引用本身都會佔用一定的記憶體,此外還有其引用的物件也佔用著記憶體。作為JNI程式設計人員,你應該注意引用同時存在的數量,特別是注意引用數量的上限,過多地建立引用,會導致記憶體耗盡。

5.2.1 Freeing Local References

在大多數情況下,不需要擔心本地函式中建立的區域性引用,會被自動釋放。然而,有時需要顯式釋放,以避免過多的記憶體佔用。考慮下面的案例:

  • 在一個函式內建立了大量的區域性引用,可能會造成JNI內部的區域性引用表溢位,最好在不需要區域性引用後立即刪除。例如下面的程式碼中,可能在遍歷一個巨大的字串陣列,每次都應該刪除區域性引用,如下:
for (i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
env->DeleteLocalRef(jstr);
}
  • 在編寫JNI工具函式時,工具函式在程式當中是公用的,被誰呼叫你是不知道的。上面MyNewString這個函式演示了怎麼樣在工具函式中使用完區域性引用後,呼叫DeleteLocalRef刪除。不這樣做的話,每次呼叫MyNewString之後,都會遺留兩個引用佔用空間(elemArr和stringClass, stringClass不用static快取的情況下)。
  • 如果你的本地函式不會返回。比如一個接收訊息的函式,裡面有一個死迴圈,用於等待別人傳送訊息過來while(true) { if (有新的訊息) { 處理之。。。。} else { 等待新的訊息。。。}}。如果在訊息迴圈當中建立的引用你不顯示刪除,很快將會造成JVM區域性引用表溢位。
  • 本地函式中需要訪問一個大物件,因此一開始就建立了一個對這個物件的引用,然後又進行了另外的複雜計算,而在這個計算過程當中是不需要前面建立的那個大物件的引用的。但是,在計算的過程當中,如果這個大物件的引用還沒有被釋放的話,會阻止GC回收這個物件,記憶體一直佔用者,造成資源的浪費。所以這種情況下,在進行復雜計算之前就應該把引用給釋放了,以免不必要的資源浪費,因此需要顯式地呼叫DeleteLocalRef。
/* A native method implementation */
JNIEXPORT void JNICALL
Java_pkg_Cls_func(JNIEnv *env, jobject this)
{
lref = ... /* a large Java object */
... /* last use of lref */
(*env)->DeleteLocalRef(env, lref);
lengthyComputation(); /* may take some time */
return; /* all local refs are freed */
}

5.2.2 Managing Local References in Java 2 SDK Release 1.2

Java 2 SDK release 1.2開始另外提供了管理區域性引用的一系列JNI函式: EnsureLocalCapacity, NewLocalRef, PushLocalFrame, PopLocalFrame。JNI規範指出,任何實現JNI規範的JVM,必須確保每個本地函式至少可以建立16個區域性引用(可以理解為虛擬機器預設支援建立16個區域性引用)。實際經驗表明,這個數量已經滿足大多數不需要和JVM中內部物件有太多互動的本地方函式。如果需要建立更多的引用,可以通過呼叫EnsureLocalCapacity函式,確保在當前執行緒中建立指定數量的區域性引用,如果建立成功則返回0,否則建立失敗,並丟擲OutOfMemoryError異常。EnsureLocalCapacity這個函式是1.2以上版本才提供的,為了向下相容,在編譯的時候,如果申請建立的區域性引用超過了本地引用的最大容量,在執行時JVM會呼叫FatalError函式使程式強制退出。在開發過程當中,可以為JVM新增-verbose:jni引數,在編譯的時如果發現原生代碼在試圖申請過多的引用時,會列印警告資訊提示我們要注意。在下面的程式碼中,遍歷陣列時會獲取每個元素的引用,使用完了之後不手動刪除,不考慮記憶體因素的情況下,它可以為這種建立大量的區域性引用提供足夠的空間。由於沒有及時刪除區域性引用,因此在函式執行期間,會消耗更多的記憶體。

/* The number of local references to be created is equal to
the length of the array. */
if ((*env)->EnsureLocalCapacity(env, len)) < 0) {
	... /* out of memory */
}
for (i = 0; i < len; i++) {
	jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
	... /* process jstr */
	/* DeleteLocalRef is no longer necessary */
}

另外,我們也可以使用Push/PopLocalFrame來建立區域性引用的內部作用域,例如把上面的例子重寫:

#define N_REFS ... /* the maximum number of local references
used in each iteration */
for (i = 0; i < len; i++) {
	if ((*env)->PushLocalFrame(env, N_REFS) < 0) {
	... /* out of memory */
	}
	jstr = (*env)->GetObjectArrayElement(env, arr, i);
	... /* process jstr */
	(*env)->PopLocalFrame(env, NULL);
}

PushLocalFrame為指定數量的區域性引用建立了新的作用域, PopLocalFrame則銷燬最上層的作用域,釋放該作用域所有的區域性引用。使用Push/PopLocalFrame函式可以管理區域性引用的生命週期,不用再時刻關注執行過程中建立的每個區域性引用。在上面的例子中,如果在處理jstr的過程當中又建立了局部引用,則PopLocalFrame執行時,這些區域性引用將全都會被銷燬。在呼叫PopLocalFrame銷燬當前frame中的所有引用前,如果第二個引數result不為空,會由result生成一個新的區域性引用,再把這個新生成的區域性引用儲存在上一個frame中。請看下面的示例:

// 函式原型
jobject (JNICALL *PopLocalFrame)(JNIEnv *env, jobject result);

jstring other_jstr;
for (i = 0; i < len; i++) {
    if ((*env)->PushLocalFrame(env, N_REFS) != 0) {
        ... /*記憶體溢位*/
    }
     jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* 使用jstr */
     if (i == 2) {
        other_jstr = jstr;
     }
    other_jstr = (*env)->PopLocalFrame(env, other_jstr);  // 銷燬區域性引用棧前返回指定的引用
}

當你寫工具函式,需要返回區域性引用時,NewLocalRef很有用.
EnsureLocalCapacity或PushLocalFrame可設定超過16個區域性引用的預設數量,Java VM會嘗試分配記憶體給超出的數量,但記憶體不一定足夠可用,如果分配失敗,VM自動退出。Java 2 SDK release 1.2支援命令列選項:-verbose:jni ,開啟後如果發現原生代碼在試圖申請過多的引用時,會列印警告資訊提示我們要注意.

5.2.3 Freeing Global References

DeleteGlobalRef, DeleteWeakGlobalRef

5.3 Rules for Managing References

前面對三種引用已做了一個全面的介紹,下面來總結一下引用的管理規則和使用時的一些注意事項,使用好引用的目的就是為了減少記憶體使用和物件被引用保持而不能釋放,造成記憶體浪費。
通常情況下,有兩種原生代碼使用引用時要注意:

  • 直接實現Java層宣告的native函式的原生代碼:要注意在迴圈中過多地建立了局部引用,和函式未返回前已無效的區域性引用,只能建立最多16個區域性引用。不要造成全域性引用和弱引用的累加,因為本地方法執行完畢後,這兩種引用不會被自動釋放。
  • 被用在任何環境下的工具函式:要當心不要在函式的呼叫軌跡上遺漏任何的區域性引用,因為工具函式被呼叫的場合和次數是不確定的,一旦被大量呼叫,就很有可能造成記憶體溢位。所以在編寫工具函式時,請遵守下面的規則:
  1. 一個返回值為基本型別的工具函式被呼叫時,它決不能造成區域性、全域性、弱全域性引用數量的累加;
  2. 當一個返回值為引用型別的工具函式被呼叫時,它除了返回的引用以外,它決不能造成其它區域性、全域性、弱引用數量的累加。
    對於工具函式來說,為了使用快取技術而建立一些全域性引用或者弱全域性引用是正常的,因為只有第一次呼叫才會生成這些引用。如果一個工具函式返回的是一個引用,我們應該寫好註釋詳細說明返回引用的型別,以便於使用者更好的管理它們。下面的程式碼中,頻繁地呼叫工具函式GetInfoString,我們需要知道GetInfoString返回引用的型別是什麼,以便於每次使用完成後呼叫相應的JNI函式來釋放掉它。
while (JNI_TRUE) {
	jstring infoString = GetInfoString(info);
	... /* process infoString */
	??? /* we need to call DeleteLocalRef, DeleteGlobalRef,
	or DeleteWeakGlobalRef depending on the type of
	reference returned by GetInfoString. */
}

函式NewLocalRef有時被用來確保一個工具函式返回一個區域性引用,我們改造一下MyNewString這個函式,演示一下這個函式的用法。下面的MyNewString是把一個被頻繁呼叫的字串“CommonString”快取在了全域性引用裡:

jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
	static jstring result;
	/* wstrncmp compares two Unicode strings */
	if (wstrncmp("CommonString", chars, len) == 0) {
		/* refers to the global ref caching "CommonString" */
		static jstring cachedString = NULL;
		if (cachedString == NULL) {
			/* create cachedString for the first time */
			jstring cachedStringLocal = ... ;
			/* cache the result in a global reference */
			cachedString = (*env)->NewGlobalRef(env, cachedStringLocal);
		}
		return (*env)->NewLocalRef(env, cachedString);
	}
	... /* create the string as a local reference and store in result as a local reference */
	return result;
}

基於全域性引用建立一個局引用返回,也同樣會阻止GC回收所引用的這個物件,因為它們指向的是同一個物件。
The Push/PopLocalFrame functions are especially convenient for managing the lifetime of local references. If you called PushLocalFrame on entry to a native function, calling PopLocalFrame before the native function returns ensures that all local references created during native function execution will be freed. The Push/PopLocalFrame functions are efficient. You are strongly encouraged to use them.
If you call PushLocalFrame on function entry, remember to call PopLocalFrame in all function exit paths. For example, the following function has one call to PushLocalFrame but needs multiple calls to PopLocalFrame:

jobject f(JNIEnv *env, ...)
{
	jobject result;
	if ((*env)->PushLocalFrame(env, 10) < 0) {
		/* frame not pushed, no PopLocalFrame needed */
		return NULL;
	}
	...
	result = ...;
	if (...) {
		/* remember to pop local frame before return */
		result = (*env)->PopLocalFrame(env, result);
		return result;
	}
	...
	result = (*env)->PopLocalFrame(env, result);
	/* normal return */
	return result;
}

Failing to place PopLocalFrame calls properly would lead to undefined behavior, such as virtual machine crashes.
The above example also illustrates why it is sometimes useful to specify the second argument to PopLocalFrame. The result local reference is initially created in the new frame constructed by PushLocalFrame. PopLocalFrame converts its second argument, result, to a new local reference in the previous frame before popping the topmost frame.

6 Exceptions

在原生代碼中,許多時候我們呼叫JNI函式並檢查返回值看是否出錯,本章將看看檢查到錯誤後怎樣從錯誤中恢復過來。
我們將關注JNI函式調用出現的錯誤,而不是原生代碼發生的錯誤,如果原生代碼有系統呼叫,怎樣檢查錯誤需要看其相應的文件。不過如果涉及到原生代碼回撥Java API的方法,必須遵照下面描述的步驟,適當地檢查可能已發生的異常並從函式執行中恢復過來。

6.1 Overview

We introduce JNI exception handling functions through a series of examples.

6.1.1 Catching and Throwing Exceptions in Native Code

下面的程式展示了怎樣宣告一個丟擲異常的本地方法:

package com.king.cmakedemo;

public class ExceptionUtil {
    static {
        System.loadLibrary("exception-lib");
    }
    public native void doit() throws IllegalArgumentException;

    private void callback() throws NullPointerException {
        throw new NullPointerException("CatchThrow.callback");
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_king_cmakedemo_ExceptionUtil_doit(
        JNIEnv* env,
        jobject obj) {

    jthrowable exc;
    jclass cls = env->GetObjectClass(obj);
    jmethodID mid =
            env->GetMethodID(cls, "callback", "()V");
    if (mid == NULL) {
        return;
    }
    env->CallVoidMethod(obj, mid);
    exc = env->ExceptionOccurred();
    if (exc) {
        /* We don't do much with the exception, except that
        we print a debug message for it, clear it, and
        throw a new exception. */
        jclass newExcCls;
        env->ExceptionDescribe();
        env->ExceptionClear();
        newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
        /* Unable to find the exception class, give up. */
            return;
        }
        env->ThrowNew(newExcCls, "thrown from C code");
    }
}

執行後列印結果:

java.lang.NullPointerException:
at CatchThrow.callback(CatchThrow.java)
at CatchThrow.doit(Native Method)
at CatchThrow.main(CatchThrow.java)
In Java:
java.lang.IllegalArgumentException: thrown from C code

callback這個Java方法丟擲NullPointerException異常,當CallVoidMethod返回後程序控制權交給原生代碼,原生代碼通過JNI函式ExceptionOccurred檢查異常,當檢測到異常時,用ExceptionDescribe輸出一條描述異常的資訊,用ExceptionClear清除異常,然後丟擲IllegalArgumentException異常來代替。
一個JNI即將丟擲的異常(通過ThrowNew)不會立即打斷本地函式的執行,這不同於Java語言的異常的行為,當Java異常發生後,VM自動將控制流程轉到try/catch對應的異常語句,相反,在異常發生後,JNI程式設計人員必須明確地實現控制流程。

6.1.2 A Utility Function

當需要丟擲自己的異常處理邏輯時,需要二步,呼叫FindClass找到異常處理類,然後呼叫ThrowNew丟擲一個異常。為了簡化操作步聚,我們寫一個工具函式,根據一個異常類名專門用來生成一個指定名字的異常:

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
{
	jclass cls = (*env)->FindClass(env, name);
	/* if cls is NULL, an exception has already been thrown */
	if (cls != NULL) {
	(*env)->ThrowNew(env, cls, msg);
	}
	/* free the local ref */
	(*env)->DeleteLocalRef(env, cls);
}

6.2 Proper Exception Handling