在 不使用IDE作一次JNI開發 一文中,咱們使用了"靜態註冊"的方法創建 Java 世界 native 方法和 Native 世界函數的一一對應關係。java
"靜態註冊"方法確實幫咱們省了不少事情,可是也有相應的缺點c++
Java_包名_類名_方法名
,例如 Java_com_bxll_jnidemo_Hello_helloFromJNI
。有"靜態註冊",固然就有"動態註冊",那麼相比較而言,有哪些優缺點呢數組
雖然"動態註冊"相比於"靜態註冊"有這麼多好處,可是須要必定的學習成本,可是這也是很是值得的,那麼咱們就開始吧。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_6
。this
若是動態庫沒有提供 JNI_OnLoad()
函數,虛擬機會假設動態庫只須要 JNI_VERSION_1_1
版本便可,然而這個版本太舊,不少新的函數都沒有,所以咱們最好在動態庫中提供這個函數,並返回比較新的版本,例如 JNI_VERSION_1_6
。spa
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);
複製代碼
參數解釋
env
: JNIEnv
指針,咱們在 鏈接Java世界的JavaVM和JNIEnv 一文中介紹過。clazz
: 表明 Java 的一個類。methos
: 表明結構體 JNINativeMethod
數組。JNINativeMethod
結構體定了Java層的native
方法和底層的函數的映射關係。nMethods
: 表明第三個參數methods
所指向的數組的大小。返回值
0表明成功,負值表明失敗。
第二個參數
jcalss clazz
如何獲取會在後面的例子中講解。
RegisterNatives()
函數最重要的部分就是 JNINativeMethod
這個結構體,咱們看下這個結構體聲明
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
複製代碼
name
表示Java
的native
方法的名字。
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
指針要指向哪一個函數呢?咱們能夠實現一個函數,就使用這個原型,可是名字能夠本身定義,而且記得去掉JNIEXPORT
和JNICALL
。
有了前面的全部基礎,那麼咱們就能夠實現本身的動態註冊功能了。
首先帶有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
向上層提供接口,這個時候就須要快速的定義Java
的native
函數,以及實現"動態註冊",若是每次都須要使用頭文件來支持"動態註冊",那麼開發效率着實的低下。那麼咱們怎麼才能快速的寫出"動態註冊"所須要的一切東西呢?那就須要對JNI
類型以及簽名很是熟悉,我會在下一篇文章中進行講解,而且讓你們看到如何最快的速度實現"動態註冊"所須要的一切。
"動態註冊"功能仍是比較簡單的,只須要搞清楚JNI_OnLoad()
和RegisterNatives()
函數的使用就行。至於具體的細節,還須要你們跟着例子仔細體會。