JNI 之二 :java & c/c++ 相互通訊及調用

 JNI是Java Native Interface的縮寫,JNI是一種機制,有了它就能夠在java程序中調用其餘native代碼,或者使native代碼調用java層的代碼。也就是說,有了JNI咱們可使Android項目中,java層與native層各自發揮所長並相互配合。 java

                                                          JAVA android

                                                             | | 數組

                                                            JNI app

                                                             | | ide

                                                         NATIVE 函數

      JNI相對與native層來講是一個接口,java層的程序想訪問native層,必須經過JNI,反過來也同樣。 this

 

1,如何告訴VM(虛擬機)java層須要調用native層的哪些libs? spa

        咱們知道java程序是運行在VM上的,而Native層的libs則否則。因此爲了讓java層能訪問native層的libs,必須得告訴VM要使用哪些native層的libs。下面看一段代碼 .net

[java]  view plain copy
  1. public class MediaPlayer    
  2.  {    
  3.      ...    
  4.      
  5.      static {    
  6.          System.loadLibrary("media_jni");    
  7.          native_init();    
  8.      }    
  9.      
  10.       ...    
  11.      
  12.      private native final void native_setup(Object mediaplayer_this);    
  13.           
  14.       ...    
  15.   }  

       能夠看到上面的代碼中,在MediaPlayer類中有一段static塊包圍起來的代碼,其中System.loadLibrary("media_jni")就是告訴VM去加載libmedia_jni.so這個動態庫,那麼這個動態庫何時被加載呢?由於static語句塊的緣由,因此在MediaPlayer第一次實例化的時候就會被加載了。這段代碼中,咱們還看到了一個函數native_init(),該函數被申明爲native型,就是告訴VM該函數由native層來實現。 線程

 

2,如何作到java層到native層的映射。

        事實上我想表達的意思是,如何完成java層的代碼到native層代碼的映射,例如上面的代碼中有一個native函數native_init(),那麼如何使這個函數映射到一個由C/C++(或者其餘語言)實現的具體函數呢?

       當VM執行到System.loadLibrary()的時候就會去執行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函數,由於JNI_OnLoad函數是從java層進入native層第一個調用的方法,因此能夠在JNI_OnLoad函數中完成一些native層組件的初始化工做,同時更加劇要的是,一般在JNI_jint JNI_OnLoad(JavaVM* vm, void* reserved)函數中會註冊java層的native方法。下面看一段代碼:

 

[java]  view plain copy
  1. jint JNI_OnLoad(JavaVM* vm, void* reserved)  
  2. {  
  3.     JNIEnv* env = NULL;  
  4.     jint result = -1;  
  5.     //判斷一下JNI的版本   
  6.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  7.         LOGE("ERROR: GetEnv failed\n");  
  8.         goto bail;  
  9.     }  
  10.     assert(env != NULL);  
  11.   
  12.     if (register_android_media_MediaPlayer(env) < 0) {  
  13.         LOGE("ERROR: MediaPlayer native registration failed\n");  
  14.         goto bail;  
  15.     }  
  16.   
  17.     if (register_android_media_MediaRecorder(env) < 0) {  
  18.         LOGE("ERROR: MediaRecorder native registration failed\n");  
  19.         goto bail;  
  20.     }  
  21.   
  22.     if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < 0) {  
  23.         LOGE("ERROR: MediaScanner native registration failed\n");  
  24.         goto bail;  
  25.     }</span>  
  26.   
  27.     if (register_android_media_MediaMetadataRetriever(env) < 0) {  
  28.         LOGE("ERROR: MediaMetadataRetriever native registration failed\n");  
  29.         goto bail;  
  30.     }  
  31.   
  32.     if (register_android_media_AmrInputStream(env) < 0) {  
  33.         LOGE("ERROR: AmrInputStream native registration failed\n");  
  34.         goto bail;  
  35.     }  
  36.   
  37.     if (register_android_media_ResampleInputStream(env) < 0) {  
  38.         LOGE("ERROR: ResampleInputStream native registration failed\n");  
  39.         goto bail;  
  40.     }  
  41.   
  42.     if (register_android_media_MediaProfiles(env) < 0) {  
  43.         LOGE("ERROR: MediaProfiles native registration failed");  
  44.         goto bail;  
  45.     }  
  46.   
  47.     /* success -- return valid version number */  
  48.     result = JNI_VERSION_1_4;  
  49.   
  50. bail:  
  51.     return result;  
  52. }  

   

         上面這段代碼的JNI_OnLoad(JavaVM* vm, void* reserved)函數實現與libmedia_jni.so庫中。上面的代碼中調用了一些形如register_android_media_MediaPlayer(env)的函數,這些函數的做用是註冊native method。咱們來看看函數register_android_media_MediaPlayer(env)的實現。

[java]  view plain copy
  1. // This function only registers the native methods  
  2. static int register_android_media_MediaPlayer(JNIEnv *env)  
  3. {  
  4.     return AndroidRuntime::registerNativeMethods(env,  
  5.                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));  

 

[java]  view plain copy
  1. /* 
  2.  * Register native methods using JNI. 
  3.  */  
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
  5.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
  6. {  
  7.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
  8. }  

          最終jniRegisterNativeMethods函數完成java標準的native函數的映射工做。下面咱們來具體的看看上面這個函數中各個參數的意義。

a,JNIEnv* env

     JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.

The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.

The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread below.)

      b,char* className,這個沒什麼好說的,java空間中類名,其中包含了包名。

      c,JNINativeMethod* gMethods,傳遞進去的是一個JNINativeMethod類型的指針gMethodsgMethods指向一個JNINativeMethod數組,咱們先看看JNINativeMethod這個結構體。

[java]  view plain copy
  1. typedef struct {  
  2.  const char* name; /*Java 中函數的名字*/  
  3.  const char* signature; /*描述了函數的參數和返回值*/  
  4.  void* fnPtr; /*函數指針,指向 C 函數*/  
  5.  } JNINativeMethod;  

再來看看gMethods數組

[java]  view plain copy
  1. static JNINativeMethod gMethods[] = {  
  2.     {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},  
  3.     。。。  
  4.     {"setAuxEffectSendLevel""(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},  
  5.     {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},  
  6.     {"getOrganDBIndex",     "(II)I",                            (void *)android_media_MediaPlayer_getOrganDBIndex},  
  7. };  

       當VM載入libxxx_jni.so這個庫時,就會呼叫JNI_OnLoad()函數。在JNI_OnLoad()中註冊本地函數,繼續調用到AndroidRuntime::registerNativeMethods(),該函數向VM(即AndroidRuntime)註冊gMethods[]數組中包含的本地函數了。AndroidRuntime::registerNativeMethods()起到了如下兩個做用:
         1,registerNativeMethods()函數使得java空間中的Native函數更加容易的找到對應的本地函數。(經過gMethods[]中的函數指針)
         2,能夠在執行期間進行本地函數的替換。由於gMethods[]數組是一個<java中函數名字,本地函數指針>的對應表,因此能夠在程序的執行過程當中,屢次呼叫registerNativeMethods()函數來更換本地函數的指針,提升程序的彈性。

 

 

函數簽名:

       在JNINativeMethod的結構體中,有一個描述函數的參數和返回值的簽名字段,它是java中對應函數的簽名信息,由參數類型和返回值類型共同組成。這個函數簽名信息的做用是什麼呢?

       因爲java支持函數重載,也就是說,能夠定義同名但不一樣參數的函數。然而僅僅根據函數名是無法找到具體函數的。爲了解決這個問題,JNI技術中就將參數類型和返回值類型的組合做爲一個函數的簽名信息,有了簽名信息和函數名,就能順利的找到java中的函數了。

        JNI規範定義的函數簽名信息格式以下:

        (參數1類型標示參數2類型標示......參數n類型標示)返回值類型標示

           實際上這些字符是與函數的參數類型一一對應的。
        「()」 中的字符表示參數,後面的則表明返回值。例如」()V」 就表示 void Func();
         「(II)V」 表示 void Func(int, int);
          值得注意的一點是,當參數類型是引用數據類型時,

           其格式是「L包名;」其中包名中的「.」換成「/」

           因此在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V 表示 void Func(String,String);

           若是 JAVA 函數位於一個嵌入類,則用$做爲類名間的分隔符。

           例如 「(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z」

 

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[]

數據類型轉換:

       在java層調用native函數傳遞到JNI層的參數,JNI層會作一些特殊處理,咱們知道java數據類型分爲基本數據類型和引用數據類型兩種,JNI層也是區別對待的。下表示出了java數據類型—>native類型的轉換。

       其中在java數據類型中,除了java中基本數據類型和數組,Class,String,Throwable,其他全部的java對象的數據類型在JNI中用jobject表示。下面來看一段代碼

[java]  view plain copy
  1. {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},  
[java]  view plain copy
  1. //java層native_invoke函數有兩個參數都是Parcel  
  2. private native final int native_invoke(Parcel request, Parcel reply);  
  3.   
  4. //JNI層對應的函數android_media_MediaPlayer_invoke的最後兩個參數與native_invoke的參數對應  
  5. android_media_MediaPlayer_invoke(JNIEnv *env, jobject thiz,  
  6.                                  jobject java_request, jobject java_reply)  


        從上面的代碼能夠看出來,java中的數據類型Parcel在JNI層對應的數據類型爲jobejct,在JNI層的對應函數中,咱們看到相對java層的native函數來講,多了兩個參數JNIEnv *env ,jobject this。第二個參數jobject表明了java層的MediaPlayer對象,它表示在哪一個MediaPlayer對象上調用的native_invoke。若是java層是static函數,那麼這個參數將是jclass,表示是在調用那個java Class的靜態函數。

        還記得前面咱們說過,java層和JNI層應該是能夠互相交互,咱們經過java層中的native函數能夠進入到JNI層,那麼JNI層的代碼能不能操做java層中函數呢?固然能夠,經過JNIEnv


JNIEnv再度解析

         先來看看兩個函數原型

[java]  view plain copy
  1. <span style="color:#000000;">jfieldID GetFieldID(jclass clazz,const char *name,const char *sig );  
  2. jmethodID GetMethodID(jclass clazz,const char *name,const char *sig);</span>  

 

         結合前面的知識來看,JNIEnv是一個與線程相關的表明JNI環境的結構體。JNIEnv實際上提供了一些JNI系統函數。經過這些系統函數能夠調用java層中的函數或者操做jobect。下面我看一段函數

[java]  view plain copy
  1. class MyMediaScannerClient : public MediaScannerClient  
  2. {  
  3. public:  
  4.     MyMediaScannerClient(JNIEnv *env, jobject client)  
  5.         :   mEnv(env),  
  6.             mClient(env->NewGlobalRef(client)),  
  7.             mScanFileMethodID(0),  
  8.             mHandleStringTagMethodID(0),  
  9.             mSetMimeTypeMethodID(0)  
  10.     {  
  11.         jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");  
  12.         if (mediaScannerClientInterface == NULL) {  
  13.             fprintf(stderr, "android/media/MediaScannerClient not found\n");  
  14.         }  
  15.         else {  
  16.             mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",  
  17.                                                      "(Ljava/lang/String;JJ)V");  
  18.             mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",  
  19.                                                      "(Ljava/lang/String;Ljava/lang/String;)V");  
  20.             mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",  
  21.                                                      "(Ljava/lang/String;)V");  
  22.             mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",  
  23.                                                      "(Ljava/lang/String;)V");  
  24.         }  
  25.     }  
  26. ...  
  27.   
  28. // returns true if it succeeded, false if an exception occured in the Java code  
  29.     virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  30.     {  
  31.         jstring pathStr;  
  32.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  33.   
  34.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  35.   
  36.         mEnv->DeleteLocalRef(pathStr);  
  37.         return (!mEnv->ExceptionCheck());  
  38.     }  

          能夠看到上面的代碼中,先找到java層中MediaScannerClinet類在JNI層中對應的jclass實例(經過FindClass)而後拿到MediaScannerclient類中所須要用到函數的函數函數id(經過GetMethodID)。接着經過JNIEnv調用CallXXXMethod函數而且把對應的jobject,jMethodID還有對應的參數傳遞進去,這樣的經過CallXXXMethod就完成了JNI層向java層的調用。這裏要注意一點的是這裏JNI層中調用的方法其實是java中對象的成員函數,若是要調用static函數可使用CallStaticXXXMethod。這種機制有利於native層回調java代碼完成相應操做。

               上面講述了以下在JNI層中去調用java層的代碼,那麼理所固然的應該能夠在JNI層中訪問或者修改java層中某對象的成員變量的值。咱們經過JNIEnv中的GetFieldID()函數來獲得java中對象的某個域的id。看下面的具體代碼

[java]  view plain copy
  1. int register_android_backup_BackupHelperDispatcher(JNIEnv* env)  
  2. {  
  3.     jclass clazz;  
  4.   
  5.     clazz = env->FindClass("java/io/FileDescriptor");  
  6.     LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");  
  7.     s_descriptorField = env->GetFieldID(clazz, "descriptor""I");  
  8.     LOG_FATAL_IF(s_descriptorField == NULL,  
  9.             "Unable to find descriptor field in java.io.FileDescriptor");  
  10.       
  11.     clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");  
  12.     LOG_FATAL_IF(clazz == NULL,  
  13.             "Unable to find class android.app.backup.BackupHelperDispatcher.Header");  
  14.     s_chunkSizeField = env->GetFieldID(clazz, "chunkSize""I");  
  15.     LOG_FATAL_IF(s_chunkSizeField == NULL,  
  16.             "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");  
  17.     s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix""Ljava/lang/String;");  
  18.     LOG_FATAL_IF(s_keyPrefixField == NULL,  
  19.             "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");  
  20.       
  21.     return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",  
  22.             g_methods, NELEM(g_methods));  
  23. }  

得到jfieldID以後呢,咱們就能夠在JNI層之間來訪問和操做java層的field的值了,方法以下

 

[java]  view plain copy
  1. NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID)  
  2.   
  3. void Set<type>Field(JNIEnv *env,jobject object ,jfieldID fieldID,NativeType value)  

注意這裏的NativeType值得是jobject,jboolean等等。

 

        如今咱們看到有了JNIEnv,咱們能夠很輕鬆的操做jobject所表明的java層中的實際的對象了。

 

jstring介紹

         之因此要把jstring單獨拿出來講正是因爲它的特殊性。java中String類型也是一個引用類型,可是JNI中並無用jobject來與之對應,JNI中單首創建了一個jstring類型來表示java中的String類型。顯然java中的String不能和C++中的String等同起來,那麼怎麼操做jstring呢?方法不少下面看幾個簡單的方法

         1,調用JNIEnv的NewStringUTF將根據Native的一個UTF-8字符串獲得一個jstring對象。只有這樣才能讓一個C++中String在JNI中使用。

         2,調用JNIEnv的GetStringChars函數(將獲得一個Unicode字符串)和GetStringUTFChars函數(將獲得一個UTF-8字符串),他們能夠將java String對象轉換誠本地字符串。下面咱們來看段事例代碼。

[java]  view plain copy
  1. virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  2.     {  
  3.         jstring pathStr;  
  4.         //將char*數組字符串轉換誠jstring類型  
  5.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  6.   
  7.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  8.   
  9.         mEnv->DeleteLocalRef(pathStr);  
  10.         return (!mEnv->ExceptionCheck());  
  11.     }  
  12.   
  13.       ....  
  14.       ....  
  15. while (env->CallBooleanMethod(iter, hasNext)) {  
  16.             jobject entry = env->CallObjectMethod(iter, next);  
  17.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  18.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  19.   
  20.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  21.             ...  
  22.             ...  

        GetStringUTFChars()函數將jstring類型轉換成一個UTF-8本地字符串,另外若是代碼中調用了上面的幾個函數,則在作完相關工做後,要調用ReleaseStringChars函數或者ReleaseStringUTFChars函數來釋放資源。看下面的代碼

 

[java]  view plain copy
  1.             ...  
  2.             ...  
  3.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  4.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  5.   
  6.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  7.             if (!keyStr) {  // Out of memory  
  8.                 jniThrowException(  
  9.                         env, "java/lang/RuntimeException""Out of memory");  
  10.                 return;  
  11.             }  
  12.   
  13.             const char* valueStr = env->GetStringUTFChars(value, NULL);  
  14.             if (!valueStr) {  // Out of memory  
  15.                 jniThrowException(  
  16.                         env, "java/lang/RuntimeException""Out of memory");  
  17.                 return;  
  18.             }  
  19.   
  20.             headersVector.add(String8(keyStr), String8(valueStr));  
  21.   
  22.             env->DeleteLocalRef(entry);  
  23.             env->ReleaseStringUTFChars(key, keyStr);  
  24.             env->DeleteLocalRef(key);  
  25.             ...  
  26.             ...  

       能夠看到GetStringUTFChars與下面的ReleaseStringUTFChars對應。

 

 

參考:  http://blog.csdn.net/mci2004/article/details/7211678

http://blog.csdn.net/mci2004/article/details/7219140

相關文章
相關標籤/搜索