android jni中的java调c的两种方法

来源:互联网 发布:camera软件下载 编辑:程序博客网 时间:2024/05/18 19:45

Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。
也就是java虚拟机通过一种机制可以找到对应的C函数 
这里就涉及到静态注册和动态注册jni函数的方法 


一.这里先说动态注册的方法
JNI 允许你提供一个函数映射表,注册给Jave虚拟机,这样Jvm/native就可以用函数映射表来调用相应的函数,
就可以不必通过函数名来查找需要调用的函数了,这种机制效率比较高
Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
typedef struct {
const char* name; //第一个变量name是Java中函数的名字。
const char* signature; //第二个变量signature,用字符串是描述了函数的参数和返回值
void* fnPtr; //第三个变量fnPtr是函数指针,指向C函数
} JNINativeMethod
当java通过System.loadLibrary加载完JNI动态库后,紧接着会查找一个JNI_OnLoad的函数,如果有,就调用它,
而动态注册的工作就是在这里完成的。


JNI_OnLoad()函数
JNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用,它有两个重要的作用:
指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,
例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。
初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当。
如:


//定义jni_java函数映射表
static JNINativeMethod gMethods[] = {
    {"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
    {"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
    {"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
};




// 注册本地方法


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env = NULL;
    jint result = -1;
    // 从虚拟机中获得JNIEnv,同时指定jni版本
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)
        return JNI_FALSE;
    // 注册本地方法
  
   //获取类名
   jclass clazz = (*env)->FindClass(env,  "com/yyj/test/classname");
  if (clazz == NULL)
        return JNI_FALSE;
//把获取到的类和方法一起注册到虚拟机中 
 if ((*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0)
        return JNI_FALSE;
    result = JNI_VERSION_1_4;
    return result;
}


其中比较难以理解的是第二个参数,这个涉及到 JNINativeMethod::signature 描述字符串字符
下面是说明:
1)基本类型对应关系:
标识符  Jni 类型       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
2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串  Jni 类型        C 类型
[I       jintArray      int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[]
[S    jshortArray   short[]
[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]
3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串        Java 类型  Jni 类型
L包1/包n/类名;     类名     jobject
例子:
Ljava/net/Socket; Socket      jobject
4)例外(String 类):
标识串               Java 类型  Jni 类型
Ljava/lang/String;  String    jstring
5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串                         Java 类型  Jni 类型
L包1/包n/类名$嵌套类名;              类名      jobject
本地动态库函数 不但可以传递 Java 的基本类型,还可以传递字符串,数组,以及自定义类
 第二个字段:
signature:它是一种对函数返回值和参数的编码。这种编码叫做JNI字段描述符(JavaNative Interface FieldDescriptors)。


例子:
"()V"      void func()        JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz )
"(II)V"     void func(int,int) JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jint,jint )
"(Ljava/lang/String;Ljava/lang/String;)V"   void func(String,String)   JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jstring )
 "(Ljava/lang/String;IFJ)F"                  float func(String,int,float,long) JNIEXPORT jfloat   JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jint,jfloat,jlong )


那我们再看看上面注册的函数怎么写
    {"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
    {"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
    {"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
如:


/*
 * Class:     com_yyj_test_classname
 * Method:  yyjfunc
 * Signature: "()Ljava/lang/String;"
 */


JNIEXPORT  jstring  Java_com_yyj_test_classname_yyjfunc(JNIEnv * env ,jobject  thiz) //需要返回一个字符串,可以这么返回 
{
char cres[512];
      jstring res;
   const char *upgrade_value;
   memset(cres, '\0', 512);
   sprintf(cres,"%s",UPFILENAME);
   res = (*env)->NewStringUTF(env, cres);
   return res;
}






/*
 * Class:     com_yyj_test_classname
 * Method:  yyjfunca
 * Signature:"(Ljava/lang/String;)I" 


JNIEXPORT  jint  Java_com_yyj_test_classname_yyjfunca(JNIEnv * env ,jobject  thiz,jstring filepath ) //需要返回一个字符串,可以这么返回 
{


    const char *pfilepath;
   pfilepath = (*env)->GetStringUTFChars(env,filepath,0);
   return 0;
}


二.还有一种办法可以让java能调到c函数,那就是静态注册
形如:JNIEXPORT jstring JNICALL Java_com_yyj_test_classname_yyjfunc
这种方式是根据Java+packname+method来实现的,名称过长,需要JNI开始时去遍历查找,效率有点低
这种方法可以先写好java代码,然后编译,生成一个 class文件,最后通过javah来自动生成jni对应函数声明


javah -jni -classpath yourclasspath  -d jni_path classname  (eg. com.yyj.test.classname)




























0 0
原创粉丝点击