歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

JNI動態注冊native方法及JNI數據使用

前言

或許你知道了jni的簡單調用,其實不算什麼百度谷歌一大把,雖然這些jni絕大多數情況下都不會讓我們安卓工程師來弄,畢竟還是有點難,但是我們還是得打破砂鍋知道為什麼這樣干吧,至少也讓我們知道調用流程和數據類型以及處理方法,或許你會有不一樣的發現。

其實總的來說從java的角度來看.h文件就是java中的interface(插座),然後.c/.cpp文件呢就是實現類罷了,然後數據類型和java還是有點出入我們還是得了解下(媽蛋,天氣真熱不適合生存了)。

今天也給出一個JNI動態注冊native方法的例子,如圖: 

JNI實現步驟

JNI 開發流程主要分為以下步驟:

  • 編寫聲明了 native 方法的 Java 類
  • 將 Java 源代碼編譯成 class 字節碼文件
  • 用 javah -jni 命令生成.h頭文件(javah 是 jdk 自帶的一個命令,-jni 參數表示將 class 中用native 聲明的函數生成 JNI 規則的函數)
  • 用本地代碼(c/c++)實現.h頭文件中的函數
  • 將(c/c++)文件編譯成動態庫(Windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
  • 拷貝動態庫至本地庫目錄下,並運行 Java 程序(System.loadLibrary(“xxx”))

我們安卓開發工程師顯然只需要編寫native的java類,然後clean下編譯器知道把我們的java編譯成了class文件,但是我們必須知道是調用了javac命令,javah jni命令我們還是得執行,其他的工作就差不多了,不管是什麼編譯器,反正jni步驟就這樣。

JVM 查找 native 方法

JVM 查找 native 方法有兩種方式:

  • 按照 JNI 規范的命名規則
  • 調用 JNI 提供的 RegisterNatives 函數,將本地函數注冊到 JVM 中。

是不是感到特別的意外,jni還能夠利用RegisterNatives 函數查找native方法,其實我也才剛剛知道有這方法,因為要根據包名類名方法名的規范來寫是很傻逼的,哈哈,有的人或許覺得這樣很直觀。

嚴格按照命名規則實現native方法的調用

我們還是按步驟來說吧,先來解讀JNI規范的命名規則:

* 我們先來看下.h文件 *


#include 

#ifndef _Included_com_losileeya_jnimaster_JNIUtils
#define _Included_com_losileeya_jnimaster_JNIUtils
#ifdef __cplusplus
extern  {
#endif

JNIEXPORT jstring JNICALL  Java_com_losileeya_jnimaster_JNIUtils_say
        (JNIEnv *, jclass,jstring);
#ifdef __cplusplus
}
#endif
#endif

我們再來看下Linux 下jni_md.h頭文件內容:

#ifndef _JAVASOFT_JNI_MD_H_  
#define _JAVASOFT_JNI_MD_H_  
#define JNIEXPORT  
#define JNIIMPORT  
#define JNICALL  
typedef int jint;  
#ifdef _LP64 /* 64-bit Solaris */  
typedef long jlong;  
#else  
typedef long long jlong;  
#endif  
typedef signed char jbyte;  
#endif  

從上面我們可以看出文件以#ifndef開始然後#endif 結尾,不會C的話是不是看起來有點蛋疼,#號呢代表宏,這裡來普及一下宏的使用和定義。

#define 標識符 字符串 
其中,#表示這是一條預處理命令;#define為宏定義命令;“標識符”為宏定義的宏名;“字符串”可以上常數、表達式、格式串等。

舉例如下:

#define PI 3.14 
void main()
{
printf(, PI); 
}

條件編譯的命令

#ifndef def
語句1
# else
語句2
# endif
表示如果def在前面進行了宏定義那麼就編譯語句1(語句2不編譯),否則編譯語句2(語句1不編譯)

再看我們.h文件並沒有else,所以我們就編譯宏定義的本地方法類(com_losileeya_jnimaster_JNIUtils),你突然就會發現我們的宏是我們的native類,然後把包名點類名的點改成了下劃線,然後你會發現多了_Included不要多想,就是included關鍵字加個下劃線,這樣我們就給本地類進行了宏定義。然後

#ifdef __cplusplus 
extern “C” {#endif

這是說明如果宏定義了c++,並且裡面有c我們還是支持c的,並且c代碼寫extern “C” {}裡面。可以看出#endif對應上面的#ifdef-cplusplus,#ifdef-cplusplus對應最後的#endif, #ifdef與#endif總是一一對應的,表明條件編譯開始和結束。

JNIEXPORT 和 JNICALL 的作用 
因為安卓是跑在 Linux 下的,所以從 Linux 下的jni_md.h頭文件可以看出來,JNIEXPORT 和 JNICALL 是一個空定義,所以在 Linux 下 JNI 函數聲明可以省略這兩個宏。 
再來看我們的方法:

JNIEXPORT jstring JNICALL  Java_com_losileeya_jnimaster_JNIUtils_say
        (JNIEnv *, jclass,jstring)

函數命名規則為:Java_類全路徑_方法名。 
如:Java_com_losileeya_jnimaster_JNIUtils_say,其中Java_是函數的前綴,com_losileeya_jnimaster_JNIUtils是類名,say是方法名,它們之間用 _(下劃線) 連接。

  • 第一個參數:JNIEnv* 是定義任意 native 函數的第一個參數(包括調用 JNI 的 RegisterNatives 函數注冊的函數),指向 JVM 函數表的指針,函數表中的每一個入口指向一個 JNI 函數,每個函數用於訪問 JVM 中特定的數據結構。
  • 第二個參數:調用 Java 中 native 方法的實例或 Class 對象,如果這個 native 方法是實例方法,則該參數是 jobject,如果是靜態方法,則是 jclass。
  • 第三個參數:Java 對應 JNI 中的數據類型,Java 中 String 類型對應 JNI 的 jstring 類型。(後面會詳細介紹 JAVA 與 JNI 數據類型的映射關系)。

函數返回值類型:夾在 JNIEXPORT 和 JNICALL 宏中間的 jstring,表示函數的返回值類型,對應 Java 的String 類型。

如果你需要裝逼的話你就可以自己去寫.h文件,然後就可以拋棄javah -jni 命令,只需要按照函數命名規則編寫相應的函數原型和實現即可(逼就是這麼裝出來的)

RegisterNatives動態獲取本地方法

是不是感覺一個方法的名字太長非常的蛋疼,然後我們呢直接使用,RegisterNatives來自己命名調用native方法,這樣是不是感覺好多了。

要實現呢,我們必須重寫JNI_OnLoad()方法這樣就會當調用 System.loadLibrary(“XXXX”)方法的時候直接來調用JNI_OnLoad(),這樣就達到了動態注冊實現native方法的作用。

/*
* System.loadLibrary()時調用
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//注冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

並且我們需要為類注冊本地方法,那樣就能方便我們去調用,不多說看方法:


static int registerNatives(JNIEnv* env) {
    return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
}

也可以為某一個類注冊本地方法


static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

JNINativeMethod 結構體的官方定義

typedef struct {  
  const char* name;  
  const char* signature;  
  void* fnPtr;  
} JNINativeMethod;  
  • 第一個變量name是Java中函數的名字。
  • 第二個變量signature,用字符串是描述了Java中函數的參數和返回值
  • 第三個變量fnPtr是函數指針,指向native函數。前面都要接 (void *)

    第一個變量與第三個變量是對應的,一個是java層方法名,對應著第三個參數的native方法名字(不明白請看後面代碼就會清楚了)。

哈哈最後我們就把native方法綁定到JNINativeMethod上我們來看下事例:

static JNINativeMethod gMethods[] = {    
    {,       ,            (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource},    
    {,    ,        (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface},    
    {,             ,                              (void *)com_media_ffmpeg_FFMpegPlayer_prepare},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_start},    
    {,               ,                              (void *)com_media_ffmpeg_FFMpegPlayer_stop},    
    {,       ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth},    
    {,      ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight},    
    {,              ,                             (void *)com_media_ffmpeg_FFMpegPlayer_seekTo},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_pause},    
    {,           ,                              (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying},    
    {,  ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition},    
    {,         ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getDuration},    
    {,            ,                              (void *)com_media_ffmpeg_FFMpegPlayer_release},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_reset},    
    {,  ,                             (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType},    
    {,         ,                              (void *)com_media_ffmpeg_FFMpegPlayer_native_init},    
    {,        ,            (void *)com_media_ffmpeg_FFMpegPlayer_native_setup},    
    {,     ,                              (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize},    
    {, ,                           (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume},    
};    

第一個參數就是我們寫的方法,第三個就是.h文件裡面的方法,第二個參數顯得有點難度,這裡會主要去講。 
主要是第二個參數比較復雜:

括號裡面表示參數的類型,括號後面表示返回值。

  • “()” 中的字符表示參數,後面的則代表返回值。例如”()V” 就表示void * Fun();
  • “(II)V” 表示 void Fun(int a, int b);
  • “(II)I” 表示 int sum(int a, int b);

這些字符與函數的參數類型的映射表如下: 
字符 Java類型 C類型 
V void void 
Z jboolean boolean 
I jint int 
J jlong long 
D jdouble double 
F jfloat float 
B jbyte byte 
C jchar char 
S jshort short

數組則以”[“開始,用兩個字符表示

[I jintArray int[] 
[F jfloatArray float[] 
[B jbyteArray byte[] 
[C jcharArray char[] 
[S jshortArray short[] 
[D jdoubleArray double[] 
[J jlongArray long[] 
[Z jbooleanArray boolean[] 
如圖: 

  • 對象類型:以”L”開頭,以”;”結尾,中間是用”/” 隔開。如上表第1個
  • 數組類型:以”[“開始。如上表第2個(n維數組的話,則是前面多少個”[“而已,如”[[[D”表示“double[][][]”)
  • 如果Java函數的參數是class,則以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名。而其對應的C函數名的參數則為jobject. 一個例外是String類,其對應的類為jstring

    Ljava/lang/String; String jstring 
    Ljava/net/Socket; Socket jobject 
     
    如果JAVA函數位於一個嵌入類,則用作為類名間的分隔符。例如“(Ljava/lang/String;LAndroid/os/FileUtilsFileStatus;)Z”

好了,所有 的介紹也完了,那麼我們就來實現我們的代碼:(果斷把h文件刪除,看效果) 
JNIUtil.c:







jstring call(JNIEnv* env, jobject thiz)
{
    return (*env)->NewStringUTF(env, );
}
jint sum(JNIEnv* env, jobject jobj,jint num1,jint num2){
    return num1+num2;
}
/**
* 方法對應表
*/
static JNINativeMethod gMethods[] = {
        {, , (void*)call},
        {, , (void*)sum},
};

/*
* 為某一個類注冊本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


/*
* 為所有類注冊本地方法
*/
static int registerNatives(JNIEnv* env) {
    return registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

/*
* System.loadLibrary()時調用
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//注冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

代碼寫完了,主要也是利用findClass來獲取方法,從而實現方法的調用。效果重現: 

demo 傳送夢:RegisterNatives.rar

JNI數據類型及常用方法(JNI安全手冊)

基本類型和本地等效類型表:

引用類型: 

接口函數表:

const struct JNINativeInterface ... = {
    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,
    NULL,
    NULL,
    NULL,
    GetSuperclass,
    IsAssignableFrom,
    NULL,

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

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NULL,
    NULL,
    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,
};

基本上jni的數據和方法都差不多放這裡了,你就可以隨便開發了。這個你也可以去看 
jni完全手冊

JNI與C/C++數據類型的轉換(效率開發)

字符數組與jbyteArray

int byteSize = (int) env->GetArrayLength(jbyteArrayData);  
unsigned char* data = new unsigned char[byteSize + 1];
env->GetByteArrayRegion(jbyteArrayData, 0, byteSize, reinterpret_cast(data));
data[byteSize] = '\0';
jbyte *jb =  (jbyte*) data;   //data是字符數組類型
jbyteArray jarray = env->NewByteArray(byteSize);   //byteSize是字符數組大小
env->SetByteArrayRegion(jarray, 0, byteSize, jb);

字符數組與jstring

char* JstringToChar(JNIEnv* env, jstring jstr) {
    if(jstr == NULL) {
        return NULL;
    }
    char* rtn = NULL;
    jclass clsstring = env->FindClass();
    jstring strencode = env->NewStringUTF();
    jmethodID mid = env->GetMethodID(clsstring, ,
            );
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}
jstring StrtoJstring(JNIEnv* env, const char* pat)
{
    jclass strClass = env->FindClass();
    jmethodID ctorID = env->GetMethodID(strClass, , );
    jbyteArray bytes = env->NewByteArray(strlen(pat));
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
    jstring encoding = env->NewStringUTF();
    return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}

特麼最簡單的可以直接使用

jstring jstr = env->NewStringUTF(str);

jint與int的互轉都可以直接使用強轉,如:

jint i = (jint) 1024;

上面的代碼你看見了嗎,都是env的一級指針來做的,所以是cpp的使用方法,如果你要轉成c的那麼就把env替換為(*env)好了,具體的方法可能有點小改動(請自行去參考jni手冊),報錯的地方請自行引入相關的.h文件,估計對你了解jni有更深的了解。

總結

本篇主要介紹了JNI動態注冊native方法,並且順便截了幾個jni的圖,以及使用的基本數據轉換處理,至於實際應用中比如java 調用c,c調用java以及混合調用等我們都需要實踐中去處理問題。

Copyright © Linux教程網 All Rights Reserved