zoukankan      html  css  js  c++  java
  • JNI 之二 :java & c/c++ 相互通信及调用

     

             JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。

                                                              JAVA

                                                                 | |

                                                                JNI

                                                                 | |

                                                             NATIVE

          JNI相对与native层来说是一个接口,java层的程序想访问native层,必须通过JNI,反过来也一样。

     

    1,如何告诉VM(虚拟机)java层需要调用native层的哪些libs?

            我们知道java程序是运行在VM上的,而Native层的libs则不然。所以为了让java层能访问native层的libs,必须得告诉VM要使用哪些native层的libs。下面看一段代码

    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方法。下面看一段代码:

     

    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)的实现。

    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));  

     

    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这个结构体。

    1. typedef struct {  
    2.  const char* name; /*Java 中函数的名字*/  
    3.  const char* signature; /*描述了函数的参数和返回值*/  
    4.  void* fnPtr; /*函数指针,指向 C 函数*/  
    5.  } JNINativeMethod;  

    再来看看gMethods数组

    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表示。下面来看一段代码

    1. {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},  

    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再度解析

             先来看看两个函数原型

    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。下面我看一段函数

    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。看下面的具体代码

    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的值了,方法如下

    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对象转换诚本地字符串。下面我们来看段事例代码。

    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函数来释放资源。看下面的代码

    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

     

     

  • 相关阅读:
    Linux常用命令大全(非常全!!!)
    TCP连接的建立与释放(三次握手与四次挥手)
    TCP/IP Http 和Https socket之间的区别
    redis持久化方法对比分析
    关于HTTP协议,一篇就够了
    远程桌面不能拷贝文件的问题
    URLDecoder: Incomplete trailing escape (%) pattern
    利用pdf2swf将PDF转换成SWF
    Oracle删除当前用户下所有的表的方法
    JS简单验证密码强度
  • 原文地址:https://www.cnblogs.com/yaozhongxiao/p/2383085.html
Copyright © 2011-2022 走看看