JNI 笔记 (总结一些基础的,常用的)

来源:互联网 发布:万人网络营销软件站 编辑:程序博客网 时间:2024/06/03 01:58

看了一些博客的内容,总结常用的。

具体是哪些,懒得去弄了。做个笔记,方便自己。

(JNI笔记 01)
 先说说几个常用的参数:
 JavaVM *g_jvm        : JavaVM 接口,g_jvm指针指向一个虚拟机进程,通过该指针,该进程下的所有线程可以获取 JNIEnv 的指针。
                                      以下函数可以获取g_jvm:
                                       jint JNI_OnLoad(JavaVM* jvm, void* reserved) ;   // java中加载动态连接库时,会自动调用这个函数,如果有的话。
                                       jint JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
                                       传入一个全局变量g_jvm,得到后就可以在任意上下文中使用,具体例子见(JNI笔记 04)

 JNIEnv *env            :JNIEnv 接口,env指针指向用来分配和存储线程本地数据的区域(如下图),不同线程的env是不同的。  
                                     可以通过以下函数获取 env ,必须传入g_jvm
                                     JNIEnv* JNU_GetEnv() 
                                    { 
                                        JNIEnv* env; 
                                        (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_4); 
                                        //(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);  这个也可以
                                        return env; 
                                     }
Jobject  thiz              :根据native方法是一个static方法还是一个实例方法而不同的。
                                       如果是一个实例方法,它就是调用该方法的对象的引用,和C++中的this指针类似(此时类型为jobject)。
                                       如果是一个static方法,那它就对应着包含该方法的类(此时类型为jclass

jfieldID  var_id                      :  成员变量的id类型 , 本质是个指针
jmethodID method_id         :  成员函数的id类型,本质是个指针
jclass  clazz                          :类的类型,在C中是void*  , C++ 中是 _jclass* 



  注意:  在C中, (*env)指向JNINativeInterface结构体的指针,可以用这个指针调用结构体的成员函数指针


===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-



(JNI笔记 02)

(1) java调用C动态库中的方法。
我把 从java调用本地C/C++代码的过程分为三层: 
java 层 :   编写一个.java文件,比如test.java,定义一个类,加载静态库: static { System.loadLibrary("pbochighapi"); }  
                                           声明要用到的native方法:public native int iCreate (byte[] dataIn, int datalen,  String str); 
                   之后就可以正常使用这个类的所有方法(本地or非本地)

JNI 层:   编写一个.c文件,属于一个桥梁的作用,将 java文件中的native声明的方法,转换为JNI调用的接口。
                 通俗易懂的说,就是转换方法名,转换参数列表(对应关系见下图)
                 以便java中的函数调用可以调用对应的函数。我们要做的大部分工作在这个地方。注意要包C底层的 .h文件
                 调用过程 : java的native函数  -----> 此文件中转换好的对应方法  -----> 每个对应方法中调用了.h文件中声明的一个方法

C 底层:  纯粹的C代码,C函数。不用任何修改。只需向上提交 .h 文件



java 和 jni  参数转换对应关系 


  其他引用映射:


Class                    ---------------   jclass
String                  ---------------   jstring
Object                 ---------------   jobject   任何对象
Object[]               ---------------  jobjectArray
boolean[]            ---------------  jbooleanArray
byte[]                   --------------   jbyteArray
char[]                   --------------   jcharArray
short[]                  --------------   jshortArray
int[]                      --------------   jintArray
long[]                  --------------    jlongArray
float[]                  --------------    jfloatArray
double[]             --------------    jdoubleArray
 

1)创建声明了native 方法的类(HelloWorld.java)。 

2)使用javac编译源文件,生成类文件(HelloWorld.class)。  javac  xxxx.java

3)使用javah -jni生成C头文件(HelloWorld.h)。                   
javah -jni XXXXX.class

4)用C、C++实现native方法(HelloWorld.c、HelloWorld.cpp),调用c动态库中的方法。(JNI层)

以上在ecplise中还需要把动态库 * .so 文件 或者  *.dll 文件放到 libs下
以上动作完成后,实现本地方法 helloworld.c文件的时候,在类似
      JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj)的方法中,
      需要把jni的参数类型转换为C类型,或者把C类型的参数转换为 JNI 类型的,就可以给C动态库的函数传入参数或者传出值
      具体 JNI <-->C 转换参数的方法见(JNI笔记03)

(2) JNI 层调用java层中的对象方法、类方法,访问对象中的属性
      在 JNI调用中,不仅仅Java可以调用本地方法,本地代码也可以调用Java中的方法和成员变量。

jclass FindClass("com/hxsmart/NativeTest") ;        // 获取一个类的class
jfieldID GetFieldID(jclass clazz, const char *name,const char *sign)                         // 取得普通成员变量的id
jfieldID GetStaticFieldID(jclass clazz, const char*name, const char *sign)               // 取得静态成员变量的id
jmethodID GetStaticMethodID(jclass clazz, const char*name, const char *sign)    // 取得静态成员函数的id
jmethodID GetMethodID(jclass clazz, const char *name,constchar *sign)               // 取得普通成员函数的id

这四个方法的参数列表都是一模一样的,每个参数的含义: 
第一个参数 jclass  clazz            :  代表我们操作的Class类,我们要从这个类里面取的属性和方法的ID。 
第二个参数 const  char *name :  这是一个常量字符数组,代表我们要取得的方法名或者变量名。 
第三个参数 const  char *sig      :  这也是一个常量字符数组,代表我们要取得的方法或变量的签名

什么是方法或者变量的签名呢? 
我们来看下面的例子,如何来获得属性和方法ID: 
public class NativeTest { 
        public void show(int i){ 
            System.out.println(i); 
        } 
        public void show(double d){ 
            System.out.println(d); 
        } 
} 
本地代码部分: 
//首先取得要调用的方法所在的类的Class对象,在C/C++中即jclass对象 
jclass clazz_NativeTest= (*env)->FindClass("com/hxsmart/NativeTest"); 
//取得jmethodID 
jmethodID id_show=(*env)->GetMethodID(clazz_NativeTest,“show”,"???"); 
上述代码中的id_show取得的jmethodID到底是哪个show方法呢?
由于Java语言有方法重载的面向对象特性,所以只通过函数名不能明确的让JNI找到Java里对应的方法。
所以这就是第三个参数sig的作用,它用于指定要取得的属性或方法的类型签名。

(1)基本类型以特定的大写字母表示

(2)Java对象以L开头,然后以“/”分隔包的完整类型,例如String的签名为:Ljava/lang/String;   注意最后面的逗号也是要的。

(3)在Java里数组类型也是引用类型,数组以[ 开头,后面跟数组元素类型的签名,例如:int[]   签名就是[I ,
         对于二维数组,如int[][] 签名就是[[I,object 数组签名就是 [Ljava/lang/Object;  



(4)   方法签名 : 

            (参数1类型签名参数2类型签名参数3类型签名.......)返回值类型签名

注意:

1. 函数名,在签名中没有体现出来

2. 参数列表相挨着,中间没有逗号,没有空格

3. 返回值出现在()后面 

例如:

Java方法                                                                   对应签名 

boolean isLedOn(void) ;                                            ()Z 
void setLedOn(int ledNo);                                         (I)V 
String substr(String str, int idx, int count);                 (Ljava/lang/String;II)Ljava/lang/String
char fun (int n, String s, int[] value);                          (ILjava/lang/String;[I)C 
boolean showMsg(View v, String msg);                     (Landroid/View;Ljava/lang/String;)Z


好了,下面我们就可以根据获取的ID调用相关的方法 和 设置、访问成员变量了。

Get方法:
第一个参数代表要获取的属性所属对象或jclass对象
第二个参数即属性ID

jXXX GetXXXField ( jobject thiz, jfieldID fieldID);            //普通
jXXX GetStaticXXXField ( jclass  thiz, jfieldID fieldID);  //静态

Set方法:
第一个参数代表要设置的属性所属的对象或jclass对象
第二个参数即属性ID
第三个参数代表要设置的值。

void SetXXXField(jobject thiz , jfieldID fieldID , jxxx val);            //普通
void SetStaticXXXField(jobject thiz , jfieldID fieldID , jxxx val);  //静态


下面是方法的调用

第一个参数代表调用的这个方法所属于的对象,或者这个静态方法所属的类。

第二个参数代表jmethodID。

第三个参数以及后面的参数,代表这个方法的参数列表

<TYPE>是返回值的类型,比如返回值是void,则 CallVoidMethod

调用普通方法:

Call<Type>Method(jobject thiz, jmethodID methodID,...);

Call<Type>MethodV(jobject thiz, jmethodID methodID,va_listargs);

Call<Type>tMethodA(jobject thiz, jmethodID methodID,const jvalue *args);

调用静态方法:

CallStatic<Type>Method(jclass clazz, jmethodID methodID,...);

CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_listargs);

CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,const jvalue *args);

例子:
(1)

// Java方法

public int show(int i,double d,char c){

}

 

// 本地调用Java方法

jint i=10L;

jdouble d=2.4;

jchar c=L'd';

(*env)->CallIntMethod(thiz , id_show , i, d, c);

(2) 用得少,略过。
(3) 略过

静态方法也是一样的。


(3) JNI 层 创建java对象
     其实就是去调用java某个类的构造函数,所以还是用到了 调用静态方法的那一套:
     jobject NewObject(jclass clazz, jmethodID methodID,...);

     jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args);

    jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) ;

步骤:  (1) jclass clazz=(*env)->FindClass("java/util/Date");

             (2)jmethodID id_date=(*env)->GetMethodID(clazz,"Date","()V");  //取得某一个构造方法的jmethodID,后面的 ()V 根据实际的构造函数写。有参数的话,括号内就写上参数签名

            (3)jobject date= (*env)->NewObject(clazz,id_date);     //调用NewObject方法创建java.util.Date对象

    

===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 笔记 03)
今天搞搞  JNI 的参数怎么和 C语言的参数进行转换,基本类型可以直接用,数组 和 引用类型需要转换。

(1)   jstring  <--->  char *buf ( char buf[] )
            
            
  1.     JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject thiz, jstring string)  
  2.     {  
  3.         // 1. jstring ---> char *
  4.         const char *str;   
  5.         str = (*env)->GetStringUTFChars(env, string,0);  //把 string转换 const char*  ,str 引用 它
  6.         if (str == NULL) {  
  7.             return NULL; /* OutOfMemoryError already thrown */  
  8.         }  
  9.         printf("%s", str);  //测试是否转换成功  // str可以拿去用了,用完就释放
  10.         //  const char * 转换 char *
  11.        int len = strlen(str);
  12.        char buf[len+1] ;
  13.        strcpy(buf,str);
  14.        printf("%s", buf); //测试是否转换成功
  15.       (*env)->ReleaseStringUTFChars(env, string , str);  // 释放引用,后面的两个参数: jstring ,const char *

  16.         // 从C底层传出的 char * ,转换为 string
  17.         // 2. char * ----> jstring
  18.         return  (*env)->NewStringUTF(buf); 
  19.     }  
以上只是其中的两个字符串函数,还有许多其他的,具体使用的时候可以依据下面的图做选择


(2)  基础类型数组  <----> int[] ,float[], double[] byte[](char [ ] , char *).........  
       实际写的函数,如果传入的是 int[],还要传入一个数组大小的参数。
 jintArray <----> int[]

JNIEXPORT jintArray JNICALL 
Java_IntArray_test(JNIEnv *env, jobject thiz, jintArray arr) 
{ 
        jint *carr; 
        jint i, sum = 0; 
        // 1. jintArray  ----> int[]
        // 获取jintArray中的 jint 所有元素
        carr = (*env)->GetIntArrayElements(env, arr, NULL);    // carr 是一个指针,指向一个array的副本
        if (carr == NULL) { 
            return 0; /* exception occurred */ 
        
       
        //使用carr[i]访问数组元素 
        int len = (*env)->GetArrayLength(env, arr); 
         int des[len];
        for (i=0; i<len; i++) { 
            des[i]= carr[i]; 
        
        // des[] 就是转换好的 int数组
        // 释放引用
        (*env)->ReleaseIntArrayElements(env, arr, carr, 0); 

         // 1. int[]  ----> jintArray
         int test[] = {1,2,3,4,5,6,7,8};  
         int len = sizeof(test)/sizeof(int) ;  // 模拟底层C函数带出来的 int test[]数组,大小也是带出来的。
         //定义数组对象 
         jintArray array  = (*env)-> NewIntArray(env, len);
        //从start开始复制长度为len 的数据 从 buffer到 array 中
        (*env)->SetIntArrayRegion(env,array,0,len,test)    
        return array;


(3) 对象数组(Strings和数组都是引用类型。你可以使用上述两个函数来访问字符串的数组和数组的数组) 

和基础类型数组不一样的是,你不能一次获取所有的对象元素,或者一次复制多个对象元素。
主要函数:

1.        (*env)->GetIntArrayElements(env,jarray, isCopy) , 返回jarray中的所有数据的指针。

           If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy ismade; if no copy is made, it is set to JNI_FALSE.

2.         (*env)->GetIntArrayRegion(env,array,start,len,buffer), 从start开始复制长度为len 的数据到buffer中 

3.        (*env)->SetIntArrayRegion(env,array,start,len,buffer) , 从start开始复制长度为len 的数据 从 buffer到 array 中

4.        jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, index);           //得到在某一个索引的值
5.        void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, index, jobject);   //设置某个索引上的值


例子:
JNIEXPORT jobjectArray JNICALL 
Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)   // 这是一个 静态方法,返回值是对象数组
{ 
        jobjectArray result; 
        int i; 
        jclass intArrCls = (*env)->FindClass(env, "[I"); // int[] 类
        if (intArrCls == NULL) { 
            return NULL;   /* exception thrown */ 
        } 
        result = (*env)->NewObjectArray(env, size, intArrCls, NULL); //生成数组类型的数组,大小为size,即二维数组
        if (result == NULL) { 
            return NULL; /* out of memory error thrown */ 
        
        
        // int[size][size]
        for (i = 0; i < size; i++)
       { 
            int tmp[256];   /* make sure it is large enough! */
            int j; 
            jintArray iarr = (*env)->NewIntArray(env, size);  //1. 生成一个int数组,在二维数组中只是一个元素
            if (iarr == NULL) { 
                return NULL; /* out of memory error thrown */ 
            
            // 2. int数组赋值,封装好一个元素
            for (j = 0; j < size; j++)
            
                tmp[j] = i + j; 
            
            //3. 将int数组作为一个元素放到 Object数组中  
            (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);   
                // env->SetIntArrayRegion(intArray, 0, size, (const int*)tmp);    如果tmp是malloc出来的,使用这种方式,并记得最后free掉。

            (*env)->SetObjectArrayElement(env, result, i, iarr); 
            (*env)->DeleteLocalRef(env, iarr); //4.  删除int数组的引用
        
        // free(tmp);
        return result; 
}

如果是string数组,与上面步骤相同,在 最后的地方改为 :
        jstring jstr; 
        char* sa[] = { "Hello,", "world!", "JNI", "很", "好玩" }; //这里是模拟字符串数组,应用中要根据实际得来的数据
        int i=0; 
        for(;i<ARRAY_LENGTH;i++) //一般数组的长度也是底层C带出来,或者传入的jarray根据jsize len =(*env)->GetArrayLength(env, jarray);
        
            jstr = (*env)->NewStringUTF( env, sa[i] ); // 差别在这里,数组的元素不同,所以生成元素的函数不同而已。

            (*env)->SetObjectArrayElement(env, texts, i, jstr);//必须放入jstring 
            (*env)->DeleteLocalRef(env, jstr); //4.  删除int数组的引用
        



===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 笔记 04)

先说下注册函数,然后开始讲 本地代码如何访问java 中的方法,属性 ,对象等

JNI注册函数 ,注册以后的jni函数的命名可以不需要符合类似javah命令生成的函数的规则
请看这篇博客:
http://blog.csdn.net/chenfeng0104/article/details/7088600 

java中调用System.loadLibrary("somelib")的时候,系统会自动调用jni的函数JNI_OnLoad 
在程序退出的时候,系统卸载“somelib”,会自动调用jni的函数JNI_OnUnload
所以感觉有点像C++ 中的构造函数 和析构函数,我们一般重写这两个方法,来做一些初始化的动作。

jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) 
{ 
                JNIEnv *env = NULL; 
                if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4))
                
                    return JNI_ERR; 
                
                g_jvm = jvm ;  // 在static变量保存 jvm指针
                sg_env= env;  // 在static 变量保存env指针

//                jclass cls = (*env)->FindClass(env, JNIT_CLASS); 
//                if (cls == NULL)  
//               { 
//                    return JNI_ERR; 
//                } 
//                jint nRes = (*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods)/sizeof(gMethods[0])); 
//                if (nRes < 0) 
//                { 
//                         return JNI_ERR; 
//                
                // 其他初始化代码:比如
                 iHxEmvInit(iIMateTestCard,iIMateResetCard,iIMateExchangeApdu,iIMateCloseCard); 
                 sendReceiveCallBack = (*sg_env)->GetMethodID(sg_env, sg_class, "sendReceiveCallBack", "([BI[BI)I"); 
                 return JNI_VERSION_1_4; 
}

OnUnLoad 就不写了!




=========================================================================
=========================================================================
在native中向LogCat输出调试信息
另外,在JNI层,可以包含 #include <android/log.h>
 __android_log_print(ANDROID_LOG_INFO, "JNIMsg"" num = %d",num); 打印JNI层的信息。
记得 加入了头文件后还必须给链接器指定__android_log_print函数所在的库文件liblog.so,
在Android.mk文件中加上一行 LOCAL_LDLIBS := -llog



关于jclass
jclass代表JAVA中的java.lang.Class。
我们看jclass的定义:
1. #ifdef __cplusplus 
2.        /*Reference types, in C++*/ 
3.       class _jobject {}; 
4.       class _jclass : public _jobject {};          /*_jclass继承_jobject*/ 
5.       typedef _jclass* jclass;
6. #else 
7.      /*Reference types, in C.*/ 
8.       typedef  void*     jobject; 
9.       typedef  jobject  jclass; 
10. #endif 

在C中jclass代表类型void*,在C++中代表类型_jclass*。
当多个native函数都需要使用同一个JAVA类的jclass变量时,不能够这样做:
定义一个 jclass 类型全局变量,并只对其赋初值一次,然后在多次对native函数调用中使用这个jclass变量。不能企图以此方式来节约获得jclass变量的开销。
jclass  StudentClazz = (*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");    $表示一个类的内部类
每次JAVA调用native都必须重新获得jclass,上次调用native所得到的jclass在下次调用native时再使用是无效的。









0 0
原创粉丝点击