您的位置:首页 > 移动开发 > Android开发

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

2016-06-25 09:03 483 查看
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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: