JNI函數動態註冊

不使用IDE作一次JNI開發 一文中,咱們使用了"靜態註冊"的方法創建 Java 世界 native 方法和 Native 世界函數的一一對應關係。java

"靜態註冊"方法確實幫咱們省了不少事情,可是也有相應的缺點c++

  1. 首次調用 Java 的 native 方法,虛擬機會去搜尋對應的 Native 層的函數,這就有點影響執行效率了。若是搜索到了,就會創建映射關係,下次就不用再浪費時間去搜索了。
  2. Native 層的函數名字太長,名字的格式爲 Java_包名_類名_方法名,例如 Java_com_bxll_jnidemo_Hello_helloFromJNI
  3. 太麻煩,影響工做效率(我的工做體驗)。

有"靜態註冊",固然就有"動態註冊",那麼相比較而言,有哪些優缺點呢數組

  1. "動態註冊"須要咱們手動創建函數映射關係,雖然增長了代碼量,可是能夠提供運行效率。
  2. "動態註冊"容許咱們自定義函數名字。
  3. 相比於"靜態註冊",工做效率高。

雖然"動態註冊"相比於"靜態註冊"有這麼多好處,可是須要必定的學習成本,可是這也是很是值得的,那麼咱們就開始吧。ide

加載動態庫

咱們知道,在 Java 層經過 System.loadLibrary() 方法能夠加載一個動態庫,此時虛擬機就會調用 JNI_OnLoad() 函數,函數原型以下函數

#include <jni.h>

jint JNI_OnLoad(JavaVM* vm, void* reserved);
複製代碼

參數介紹post

  • vm: JavaVM 指針,咱們在 鏈接Java世界的JavaVM和JNIEnv 一文中介紹過。
  • reserved: 類型爲 void *,這個參數是爲了保留位置,以供未來使用。

返回值表明被動態庫須要的JNI版本,固然,若是虛擬機沒法識別這個返回的版本,那麼動態庫也加載不了。學習

目前已有的返回值有四個,分別爲 JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6this

若是動態庫沒有提供 JNI_OnLoad() 函數,虛擬機會假設動態庫只須要 JNI_VERSION_1_1 版本便可,然而這個版本太舊,不少新的函數都沒有,所以咱們最好在動態庫中提供這個函數,並返回比較新的版本,例如 JNI_VERSION_1_6spa

註冊函數

JNI_OnLoad() 函數常常會用來作一些初始化操做,"動態註冊"就是在這裏進行的。指針

"動態註冊"能夠經過調用_JNIEnv結構體的 RegisterNatives() 函數

struct _JNIEnv {

    const struct JNINativeInterface* functions;

#if defined(__cplusplus)
    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) { return functions->RegisterNatives(this, clazz, methods, nMethods); }
#endif /*__cplusplus*/
}
複製代碼

實際使用的函數原型以下

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
複製代碼

參數解釋

  1. env: JNIEnv指針,咱們在 鏈接Java世界的JavaVM和JNIEnv 一文中介紹過。
  2. clazz: 表明 Java 的一個類。
  3. methos: 表明結構體 JNINativeMethod 數組。JNINativeMethod結構體定了Java層的native方法和底層的函數的映射關係。
  4. nMethods: 表明第三個參數methods所指向的數組的大小。

返回值

0表明成功,負值表明失敗。

第二個參數jcalss clazz如何獲取會在後面的例子中講解。

JNINativeMethod結構體

RegisterNatives() 函數最重要的部分就是 JNINativeMethod 這個結構體,咱們看下這個結構體聲明

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
複製代碼

name表示Javanative方法的名字。

signature表示方法的簽名。

fnPtr是一個函數指針,指向JNI層的一個函數,也就是和Java層的native創建映射關係的函數。

那麼這個三個參數如何指定呢?我首先教你們一個偷懶的方法,咱們在不使用IDE作一次JNI開發使用javah命令生成過一個頭文件,函數原型以下

/* * Class: com_bxll_jnidemo_Hello * Method: helloFromJNI * Signature: ()Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_bxll_jnidemo_Hello_helloFromJNI (JNIEnv *, jclass);
複製代碼

JNINativeMethod 結構體中的 name 的值就對應註釋的 method 的值,也就是 helloFromJNI

JNINativeMethod 結構體中的 signature 的值就對應註釋的 Signature 的值,也就是 ()Ljava/lang/String;

JNINativeMethod 結構體中的 fnPtr 指針要指向哪一個函數呢?咱們能夠實現一個函數,就使用這個原型,可是名字能夠本身定義,而且記得去掉JNIEXPORTJNICALL

實現動態註冊

有了前面的全部基礎,那麼咱們就能夠實現本身的動態註冊功能了。

首先帶有native方法的Java類以下

package com.bxll.jnidemo;

class Hello {
    native String helloFromJNI();
}
複製代碼

而後咱們可使用javah命令生成頭文件來幫咱們準確無誤的實現動態註冊,生成的頭文件中函數原型以下

/* * Class: com_bxll_jnidemo_Hello * Method: helloFromJNI * Signature: ()Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_bxll_jnidemo_Hello_helloFromJNI (JNIEnv *, jclass);
複製代碼

根據頭文件的註釋和函數原型,咱們就能夠實現以下的動態註冊

#include <jni.h>

static jstring native_helloFromJNI(JNIEnv *env, jobject thiz) {
    const char *hello = "Hello from C++";
    return env->NewStringUTF(hello);
}


const JNINativeMethod methods[] = {
        {"helloFromJNI", "()Ljava/lang/String;", (void *) native_helloFromJNI}
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    int jniVersion = -1;
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
        // 找到com.bxll.jnidemo.Hello類,只不過參數須要把點號替換爲下劃線
        jclass clazz_hello = env->FindClass("com_bxll_jnidemo_Hello");
        if (env->RegisterNatives(clazz_hello, methods,
                                 sizeof(methods) / sizeof(methods[0])) == JNI_OK) {
            jniVersion = JNI_VERSION_1_6;
        }
    }

    return jniVersion;
}
複製代碼

必需要引入頭文件 jni.h,這個頭文件是JNI所必須的。

提高工做效率

我是一個Android開發者,在項目中常常會遇到在底層開發一個功能,而後須要經過JNI向上層提供接口,這個時候就須要快速的定義Javanative函數,以及實現"動態註冊",若是每次都須要使用頭文件來支持"動態註冊",那麼開發效率着實的低下。那麼咱們怎麼才能快速的寫出"動態註冊"所須要的一切東西呢?那就須要對JNI類型以及簽名很是熟悉,我會在下一篇文章中進行講解,而且讓你們看到如何最快的速度實現"動態註冊"所須要的一切。

總結

"動態註冊"功能仍是比較簡單的,只須要搞清楚JNI_OnLoad()RegisterNatives()函數的使用就行。至於具體的細節,還須要你們跟着例子仔細體會。

相關文章
相關標籤/搜索