如何在jni中注册native函数,有几种注册方式?

Java如何调用c、c++语言?

 

注册JNI函数的两种方法

静态方法

这种方法我们比较常见,但比较麻烦,大致流程如下:

  1. 先创建Java类,声明Native方法,编译成.class文件。
  2. 使用Javah命令生成C/C++的头文件,例如:javah -jni com.devilwwj.jnidemo.TestJNI,则会生成一个以.h为后缀的文件com_devilwwj_jnidemo_TestJNI.h。
  3. 创建.h对应的源文件,然后实现对应的native方法

 

这种方法的弊端:

  1. 需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah命令生成一个头文件。
  2. javah生成的JNI层函数名特别长,书写起来很不方便
  3. 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率

 

动态注册

我们知道Java Native函数和JNI函数时一一对应的,JNI中就有一个叫JNINativeMethod的结构体来保存这个对应关系,实现动态注册方就需要用到这个结构体。举个例子,你就一下子明白了:

1.声明native方法还是一样的:

public class JavaHello {
    public static native String hello();
}

​​​​​​2.创建jni目录,然后在该目录创建hello.c文件,如下:

#include 
#include 
#include 
#include 
#include 

/**
 * 定义native方法
 */
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
    printf("hello in c native code./n");
    return (*env)->NewStringUTF(env, "hello world returned.");
}

// 指定要注册的类
#define JNIREG_CLASS "com/devilwwj/library/JavaHello"

// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法
static JNINativeMethod gMethods[] = {
    { "hello", "()Ljava/lang/String;", (void*)native_hello},
};

static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/***
 * 注册native方法
 */
static int registerNatives(JNIEnv* env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/**
 * 如果要实现动态注册,这个方法一定要实现
 * 动态注册工作在这里进行
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)-> GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) { //注册
        return -1;
    }
    result = JNI_VERSION_1_4;

    return result;

}

先仔细看一下上面的代码,看起来好像多了一些代码,稍微解释下,如果要实现动态注册就必须实现JNI_OnLoad方法,这个是JNI的一个入口函数,我们在Java层通过System.loadLibrary加载完动态库后,

  1. 紧接着就会去查找一个叫JNI_OnLoad的方法。如果有,就会调用它,而动态注册的工作就是在这里完成的。在这里我们会去拿到JNI中一个很重要的结构体JNIEnv,env指向的就是这个结构体,通过env指针可以找到指定类名的类,并且调用JNIEnv的RegisterNatives方法来完成注册native方法和JNI函数的对应关系。
  2. 我们在上面看到声明了一个JNINativeMethod数组,这个数组就是用来定义我们在Java代码中声明的native方法,我们可以在jni.h文件中查看这个结构体的声明:
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

结构体成员变量分别对应的是Java中的native方法的名字,如本文的hello;Java函数的签名信息、JNI层对应函数的函数指针。

 

区别:

  1. 静态注册
    优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
    缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
  2. 动态注册
    优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
    缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

 

你可能感兴趣的:(Android,NDK,jni,Binder,AIDL等)