Java中JNI的使用(上)
JNI 在 Java 1.1中正式推出,在 Java 1.2版本中加入了 JNI_OnLoad、JNI_OnUnload 方法,這兩個方法還是很有用的,後面再說。
JNI基礎篇
Java 通過 JNI 調用本地方法的過程大致是:
寫一個 Java 類,在其中聲明對應要調用的 native 方法,用 native 關鍵字修飾。 比如 private static native int native_newInstance();
通過 javah 命令生成 Java 類對應的 C/C++ 頭文件。javah -encoding utf-8 -cp src com.young.soundtouch.SoundTouch;
編譯 C/C++ 代碼為動態庫(Windows中的dll、Linux/Android 中的 so、MAC OSX 中的 dylib);
在 Java 代碼中加載動態庫,即可像調用 Java 方法一樣,調用到 native 函數。
其中第3步在 Java 1.2 中增加了 JNI_OnLoad 方法之後有另一種實現方式(後面說)。
javah 生成的頭文件大致是這樣的:
/ DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_young_soundtouch_SoundTouch
#ifndef _Included_com_young_soundtouch_SoundTouch
#define _Included_com_young_soundtouch_SoundTouch
#ifdef __cplusplus
extern "C" {
#endif
#undef com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER
#define com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER 0L
/*
- Class: com_young_soundtouch_SoundTouch
- Method: native_getDefaultSampleElementSize
- Signature: ()I
/
JNIEXPORT jint JNICALL Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize
(JNIEnv , jclass);
#ifdef __cplusplus
}
#endif
#endif
文件開頭就是普通的頭文件,但是可以發現:
包含了 jni.h 頭文件(一般位於 $JAVA_HOME/jd{jdk-version}/include 文目錄內)。這是 JNI 中所有的類型、函數、宏等定義的地方。所以C/C++世界的JNI是由他制定的遊戲規則。
在類中生命的常量(static final)類型會在頭文件中以宏的形式出現,這一點還是很方便的。
函數的註釋還是比較全的,包括了:
對應的 class
對應的 Java 方法名
對應 Java 方法的簽名
方法的聲明顯得有點奇怪,由以下及部分組成:
JNIEXPORT這是函數的導出方式;
jint 返回值類型(jint 由 jni.h 定義,對應 int,下面具體再說吧);
JNICALL 函數的調用方式也就是匯編級別參數的傳入方式;
Java_com_young_soundtouch_SoundTouch_native1getDefaultSampleElementSize —— 超級長的函數名!!!格式是 Java + 類全名 + + JAVA 中聲明的native方法名。其中會把包名中的點(.)替換成下劃線(),同時為了避免沖突把下劃線替換成_1;
方法的參數,上面的這個方法在 Java 的聲明中實際上是沒有參數的,其中的 JNIENV 顧名思義是 JNI 環境,和具體的線程綁定。而第二個參數 jclass 其實是 Java 中的 Class。因為上面是一個 static 方法,因此第二個參數是 jclass。如果是一個實例方法則對應第二個參數是 jobject,相當於 Java中的 this。
下面在 C/C++ 中實現這個方法就行啦。但是在動手前現大致了解以下 jni.h 制定的遊戲規則。
類型轉換
javah 生成的頭文件裏面使用的類型都是 jni.h 定義的,目的是做到平臺無關,比如保證在所有平臺上 jint 都是32位的有符號整型。
基本對應關系如下:
引用類型對應關系:
通過表格發現,除了上面定義的 String、Class、Throwable,其他的類(除了數組)都是以 jobject 的形式出現的!事實上 jstring、 jclass 也都是 object 的子類。所以這裏還是和 Java 層一樣,一切皆 jobject。(當然,如果 jni 在 C 語言中編譯的話是沒有繼承的概念的,此時 jstring、jclass 等其實就是 jobject!用了 typedef 轉換而已!!)
接下來是 JNIEnv * 這個指針,它提供了 JNI 中的一系列操作的接口函數。
JNI 中操作 jobject
其實也就是在native層操作 Java 層的實例。 要操作一個實例無疑是:
獲取/設置 (即 get/set )成員變量(field)的值;
調用成員方法(method)。
所以問題來了:(挖掘機技術哪家強?! o(*≧▽≦)ツ┏━┓ )
怎麽得到 field 和 method?
通過使用 jfieldID 和 jmethodID: 在 JNI 中使用類似於放射的方式來進行 field 和 method 的操作。JNI 中使用j fieldID 和 jmethodID 來表示成員變量和成員方法,獲取方式是:
jfieldID GetFieldID(jclass clazz, const char name, const char sig);
jfieldID GetStaticFieldID(jclass clazz, const char name, const char sig);
jmethodID GetMethodID(jclass clazz, const char name, const char sig);
jmethodID GetStaticMethodID(jclass clazz, const char name, const char sig) ;
其中最後一個參數是簽名。 獲取jclass的方法除了實用上面靜態方法的第二個參數外,還可以手動獲取。 jclass FindClass(const char *name) 需要註意的是name參數,他是一個類包括包名的全稱,但是需要把包名中的點.替換成斜杠/。(好吧,事實上我不是太明白為啥要這麽做。)
有了 jfieldID 和 jmethodID 就知道狗蛋住哪了,現在去狗蛋家找他玩 ?(^?^*)
- get:
<type> Get<type>Field(jobject , jfieldID); 即可獲得對應的field,其中field的類型是type,可以是上面類型所敘述的任何一種
<type> GetStatic<type>Field(jobject , jfieldID); 同1,唯一的區別是用來獲取靜態成員。
- set:
void Set<type>Field(jobject obj, jfieldID fieldID, <type> val)
void SetStatic<type>Field(jclass clazz, jfieldID fieldID, <type> value);
成員方法:
調用方法自然要把方法的參數傳遞進去,JNI中實現了三種參數的傳遞方式:
Call<type>Method(jobject obj, jmethod jmethodID, ...) 其中 ... 是 C 中的可變長參數,類似於 printf 那樣,可以傳遞不定長個參數。於是你可以把 Java 方法需要的參數在這裏面傳遞進去。
Call<type>MethodV(jobject obj, jmethodID methodID, va_list args) 其中的 va_list 也是 C 中可變長參數相關的內容(我不了解,不敢瞎說,偷懶粘一下Oracle的文檔)“Programmers place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. The CallMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.”
Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue args) 哎!這個我知道可以說兩句 LOL ~~這裏的 jvalue 通過查代碼發現就是 JNI 中各個數據類型的 union,所以可以使用任何類型復制!所以參數的傳入方式是通過一個 jvalue 的數組,數組內的元素可以是任何 jni 類型。
然後問題又來了:(挖掘機技術到底哪家強?!o(≧▽≦)ツ┏━┓) 如果傳進來的參數和java聲明的參數的不一致會怎麽樣!(即不符合方法簽名)這裏文檔中沒用明確解釋,但是說道: > Exceptions raised during the execution of the Java method.
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
- 調用實例方法(instance method):
<type> Call<type>Method(jobject obj, jmethodID methodID, ...); 調用一個具有<type>類型返回值的方法。
<type> Call<type>MethodV(jobject obj, jmethodID methodID, va_list args);
Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)
- 調用靜態方法(static method):
<type> CallStatic<type>Method(jobject obj, jmethodID methodID, ...);
<type> CallStatic<type>MethodV(jobject obj, jmethodID methodID, va_list args);
CallStatic<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)
- 調用父類方法(super.method),這個就有點不一樣了。多了一個 jclass 參數,jclass 可以使 obj 的父類,也可以是 obj 自己的class,但是 methodID 必須是從 jclass 獲取到的,這樣就可以調用到父類的方法。
<type> CallNonvirtual<type>Method(jobject obj, jclass clazz, jmethodID methodID, ...)
<type> CallNonvirtual<type>MethodV(JNIEnv env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
<type> CallNonvirtual<type>MethodA(JNIEnv env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);
Java中JNI的使用(上)