JNI基礎知識(JNI Design Overview)

1、JNI “接口函數” 和 “接口指針

Native代碼通過調用JNI函數來訪問Java VM特性。
JNI函數可通過“接口指針”調用。
接口指針”是一個指向“某一指針” 的 指針。
“某一指針”又指向一個指針數組,指針數組中的每個指針,都指向一個“接口函數
在這裏插入圖片描述
2、JNI類型和數據結構
主要講,Jni怎麼將java類型 映射到 C原生類型
2.1 基本類型

Java Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

方便起見,還有如下定義:

#define JNI_FALSE  0 
#define JNI_TRUE   1 
typedef jint jsize; 

2.2 引用類型
在C語言中:

typedef jobject jclass; 

在C++語言中:

class _jobject {}; 
class _jclass : public _jobject {}; 
... 
typedef _jobject *jobject; 
typedef _jclass *jclass; 

引用類型
2.3 Value類型

typedef union jvalue { 
    jboolean z; 
    jbyte    b; 
    jchar    c; 
    jshort   s; 
    jint     i; 
    jlong    j; 
    jfloat   f; 
    jdouble  d; 
    jobject  l; 
} jvalue; 

2.4 類型簽名

Type Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type

示例:

For example, the Java method:

long f (int n, String s, int[] arr); 

has the following type signature:

(ILjava/lang/String;[I)J 

3、加載、關聯Native方法(靜態註冊)

加載:Native方法通過System.loadLibrary方法來實現加載。
如果底層操作系統不支持動態鏈接,所有Native方法必須預鏈接到VM中,此時VM調用System.loadLibrary,但並沒有實際加載這個library庫。
另外,還可以用JNI方法RegisterNatives()方法來註冊Native方法關聯到class類,這個方法對靜態鏈接方法很有用。

解析Native方法名:
動態連接器是基於名字來解析條目的,而一個Native方法的名字由一下幾個方面組成:

  • 前綴Java_
  • 拆開的完全限定的類名
  • 下劃線分隔符
  • 如果是重載Native方法,需要兩個下劃線(__)後跟上拆開的參數簽名

VM是通過方法名匹配Native庫中的方法。VM優先查找短名,即,沒有參數簽名的方法名。然後纔會查找長名,即,有參數簽名的方法名。
只有在重載Native方法時,才用長名。
我們使用下劃線("_")來替換完全限定的類名中的斜槓("/")
另外,
由於方法名、類型不能以數字開頭,所以我們可以用_0,…,_9來做轉義符。
_0XXXX : 表示一個Unicode字符XXXX
_1 : 表示單下劃線字符 “__”
_2 : 表示分號字符“;”
_3 : 表示左中括號字符“[”
舉例:
Native方法f:
native double f(int i, String s);
C函數中對應的長名:
Java_pkg_Cls_f__ILjava_lang_String_2

C函數方法實現示例:

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
     JNIEnv *env,        /* interface pointer */
     jobject obj,        /* "this" pointer */
     jint i,             /* argument #1 */
     jstring s)          /* argument #2 */
{
     /* Obtain a C-copy of the Java string */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* process the string */
     ...

     /* Now we are done with str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

C++函數聲明:

extern "C" /* specify the C calling convention */  

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( 

     JNIEnv *env,        /* interface pointer */ 

     jobject obj,        /* "this" pointer */ 

     jint i,             /* argument #1 */ 

     jstring s)          /* argument #2 */ 

{ 

     const char *str = env->GetStringUTFChars(s, 0); 

     ... 

     env->ReleaseStringUTFChars(s, str); 

     return ... 

} 

3.3 靜態註冊和動態註冊
除了上面說的靜態註冊,還有一種動態註冊的方法。

4、JNIEnv 和 jobject的理解與使用

JNIEnv 和 jobject是native方法的C語言實現的固定的前兩個參數。
The JNI interface pointer is the first argument to native methods. The JNI interface pointer is of type JNIEnv.
The second argument differs depending on whether the native method is static or nonstatic. The second argument to a nonstatic native method is a reference to the object. The second argument to a static native method is a reference to its Java class.

JNIEnv :

每個函數都有一個固定的參數JNIEnv,這個JNIEnv類型是一個指針,指向存儲了所有JNI函數指針的數據結構,定義如下:

typedef const struct JNINativeInterface *JNIEnv; 

const struct JNINativeInterface ... = {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    GetSuperclass,
    IsAssignableFrom,

    ToReflectedField,

    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,

    PushLocalFrame,
    PopLocalFrame,

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NewLocalRef,
    EnsureLocalCapacity,

    AllocObject,
    NewObject,
    NewObjectV,
    NewObjectA,

    GetObjectClass,
    IsInstanceOf,

    GetMethodID,

    CallObjectMethod,
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,

    CallNonvirtualObjectMethod,
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,

    GetFieldID,

    GetObjectField,
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,

    GetStaticMethodID,

    CallStaticObjectMethod,
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,

    GetStaticFieldID,

    GetStaticObjectField,
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,

    SetStaticObjectField,
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,

    NewString,

    GetStringLength,
    GetStringChars,
    ReleaseStringChars,

    NewStringUTF,
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,

    GetArrayLength,

    NewObjectArray,
    GetObjectArrayElement,
    SetObjectArrayElement,

    NewBooleanArray,
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,

    GetBooleanArrayElements,
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,

    ReleaseBooleanArrayElements,
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,

    GetBooleanArrayRegion,
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,

    RegisterNatives,
    UnregisterNatives,

    MonitorEnter,
    MonitorExit,

    GetJavaVM,

    GetStringRegion,
    GetStringUTFRegion,

    GetPrimitiveArrayCritical,
    ReleasePrimitiveArrayCritical,

    GetStringCritical,
    ReleaseStringCritical,

    NewWeakGlobalRef,
    DeleteWeakGlobalRef,

    ExceptionCheck,

    NewDirectByteBuffer,
    GetDirectBufferAddress,
    GetDirectBufferCapacity,

    GetObjectRefType
  };

具體函數API參考:JNI Functions

jobject :

作爲函數第二個固定參數,jobject表示native方法所在的java類 / java對象的引用。
如果C函數不寫jobject參數,那麼會報錯:
Missing parameter: ‘jobject thiz’

如何使用呢?
1、我們可以用jobject參數,獲取java類/java對象中的其他方法:
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
該方法中的jclass,就可以用jobject,應爲jclass是有jobject派生出來的。
2、獲取靜態字段:
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
3、等等,參考接口函數表(JNI Functions)

5、extern “C”

在Cpp文件中,開發JNI需要加上extern “C” ,那是因爲:

C++支持重載,編譯後會將參數和返回值都加到方法名上。
而C語言不支持重載,所以編譯後的方法名就是原方法名,
JNI鏈接方法是通過方法名來鏈接的,所以只能用C語言的規範,
所以需要在cpp文件中加上extern “C” ,使其按照C語言的方式編譯。
而c文件,就沒必要加extern “C”,因爲C語言不支持extern “C” 語句,也沒必要加。

6、JNIEXPORT和JNICALL

首先開發過程中,調試程序,不加JNIEXPORT和JNICALL依然可以運行,但並不代表JNIEXPORT和JNICALL沒用。
在jni.h中,他們是這樣聲明的:

#define JNIEXPORT  __attribute__ ((visibility ("default")))
#define JNICALL

由此可知,JNIEXPORT是生命可見性的,默認是default;
JNICALL是空的宏定義。這就解釋了爲什麼不寫JNIEXPORT和JNICALL依然可以運行。

那麼如果不寫JNIEXPORT和JNICALL會怎樣?
沒有jniexport會導致某些機型出現No implementation found for native method.
沒有jnicall會導致奇怪的堆棧損壞問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章