JNI 方法注册与签名

来源:互联网 发布:js contains方法 编辑:程序博客网 时间:2024/05/20 03:41

最近了解了关于Java JNI接口的一些关于方法注册与签名相关的知识,在此进行一下总结。

使用JNI接口时,我们首先需要把Java方法声明为native:

public native void f();

然后编写对应的C/C++代码,并编译成为动态链接库(.dll或.so),在调用Java方法前载入动态链接库即可调用:

    static {        System.loadLibrary("native-lib");    }

那么,Java文件中的native方法是如何与native文件中的方法一一对应的呢?

在此有两种方法:静态注册与动态注册,下面将一一介绍:

静态注册

采用静态注册时,Java层的native方法与native层的方法在名称上具有一一对应的关系,具体要求如下:
native层的方法名为:Java_<包名>_<类名>_<方法名>(__<参数>)
其中,包名使用下划线代替点号进行分割
只有当native方法出现需要重载的时候,native层的方法名后才需要跟上参数(括号里的内容),参数的编写形式与JNI签名相关(后面会介绍)
通常而言,我们可以把native方法集中在一个类中,然后调用:
javah -jni 包名.类名

自动生成对应的c层头文件
下面是静态注册的例子:
Java层:
package com.app.superxlcr.jnitest;/** * Created by superxlcr on 2017/5/25. */public class NativeTest {    public native void f();    public native int f(int a, double b);    public native void f(Object a, String b);    public native void g();}

native层:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_app_superxlcr_jnitest_NativeTest */#ifndef _Included_com_app_superxlcr_jnitest_NativeTest#define _Included_com_app_superxlcr_jnitest_NativeTest#ifdef __cplusplusextern "C" {#endif/* * Class:     com_app_superxlcr_jnitest_NativeTest * Method:    f * Signature: ()V */JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__  (JNIEnv *, jobject);/* * Class:     com_app_superxlcr_jnitest_NativeTest * Method:    f * Signature: (ID)I */JNIEXPORT jint JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__ID  (JNIEnv *, jobject, jint, jdouble);/* * Class:     com_app_superxlcr_jnitest_NativeTest * Method:    f * Signature: (Ljava/lang/Object;Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__Ljava_lang_Object_2Ljava_lang_String_2  (JNIEnv *, jobject, jobject, jstring);/* * Class:     com_app_superxlcr_jnitest_NativeTest * Method:    g * Signature: ()V */JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_g  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif

我们可以看到,对于拥有重载的f 方法,其native方法名称后都带有参数,而没有重载的g 方法则没带有
静态注册JNI方法的弊端非常明显,就是方法名会变得很长,因此下面我们介绍另外一种动态注册的方法

动态注册

使用动态注册时,我们需要准备好需要自己想要对应的native方法,然后构造JNINativeMethod数组,JNINativeMethod是一种结构体,源码如下:
typedef struct {// Java层native方法名称    const char* name;// 方法签名    const char* signature;// native层方法指针    void*       fnPtr;} JNINativeMethod;

然后重写JNI_OnLoad方法(该方法会在Java层通过System.loadLibrary加载完动态链接库后被调用),我们在其中进行动态注册工作:
JNIEXPORT jint JNICALLJNI_OnLoad(JavaVM* vm, void* reserved) {    JNIEnv *env = NULL;    jint result = -1;    // 获取JNI env变量    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {        // 失败返回-1        return result;    }    // 获取native方法所在类    const char* className = "com/app/superxlcr/jnitest/MainActivity";    jclass clazz = env->FindClass(className);    if (clazz == NULL) {        return result;    }    // 动态注册native方法    if (env->RegisterNatives(clazz, methods, 1) < 0) {        return result;    }    // 返回成功    result = JNI_VERSION_1_4;    return result;}

动态注册的大致步骤如下:
  1. 通过vm(Java虚拟机)参数获取JNIEnv变量
  2. 通过FindClass方法找到对应的Java类
  3. 通过RegisterNatives方法,传入JNINativeMethod数组,注册native函数
对于JNINativeMethod结构而言,签名是其非常重要的一项元素,它用于区分Java中native方法的各种重载形式,下面将介绍方法的签名

方法签名

方法签名对于区分Java层native重载方法有重大意义
总的来说,方法签名的组成规则为:
(参数类型标识1参数类型标识2...参数类型标识n)返回值类型标识

类型标识对应关系如下:
类型标识Java类型ZbooleanBbyteCcharSshortIintJlongFfloatDdoubleL包名/类名;各种引用类型Vvoid
另外,当Java类型为数组时,在标识前会有“[”符号,例如:String[] 类型标识为 [Ljava/lang/String;

下面举几个例子:
// Signature: ()Vpublic native void f();// Signature: (ID)Ipublic native int f(int a, double b);// Signature: (Ljava/lang/Object;Ljava/lang/String;)Vpublic native void f(Object a, String b);// Signature: ()Vpublic native void g();