JNI技术之动态注册

JNI技术之动态注册

一.静态注册的缺点
1.C/C++实现的函数名,必须通过javah工具生成对应的C/C++的头文件。
2.初次调用native函数时,采用的是在动态库中寻找,如果native函数较多的话,效率比较低下的。

如果能在java层将所有的native函数直接将与C/C++实现的函数关联起来,那我们在调用C/C++实现的函数时,就不需要到动态库中寻找对应的函数接口。提供了运行效率。

二.动态注册

如何将java native函数和C/C++函数关联起来,Jni中有如下结构体。

/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */

typedef struct {
    char *name; //java层native函数名
    char *signature;//签名信息,用字符串表示,是返回值和参数的结合
    void *fnPtr;//记录JNI层函数的入口地址
} JNINativeMethod;

JNI层C/C++的代码:

#include <jni.h>
#include "Test.h"
#include <stdio.h>

int jni_sayHello(JNIEnv *env, jobject obj) {
    printf("hello world!!!\n");
    return 0;
}

static JNINativeMethod gMethods[] = {
    {
        "sayHello",
        "()V",
        (void *)jni_sayHello
    },
};

在JNI层首先要填充JNINativeMethod类型的结构体,JAVA层可能有多个native函数,所以这里以结构体数组的形式呈现的,你可以添加所有JAVA层native函数和JNI层函数的一一对应关系。

关键是签名信息,是由java层native函数的返回值类型和参数类型组成的信息。原因是java函数支持函数重载。签名信息正好可以进行区分的。

JNI签名信息:
格式:
(参数1类型标识 参数2类型标识 …参数n类型标识)返回值类型标识

例如:JAVA层的native函数
void testFuction(int a, short b, char c, String str, HelloWorld obj);

它对应的JNI层签名信息如下:
(ISCLjava/lang/String;LHelloWorld;)V

其中括号内部是参数类型的标识,最右边是返回值类型标识的。

在这里插入图片描述
Java提供了一个javap的工具能帮助生成函数或变量的签名信息,它的用法如下:
javap -s -p xxx

其中xxx为编译后的class文件名,s表示输出内部数据类型的签名信息,p表示打印函数和成员的签名信息,默认只会打印public成员和函数的签名信息。

先用javac Test.java
再用javap -s -p Test
在这里插入图片描述
我们知道了JAVA层native函数和JNI层函数间的关系,还需要将它们之间的关系告知java虚拟机。

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;
	
	//获取JNIEnv环境
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        printf("JavaVm fail to get JNIEnv\n");
        return -1;
    }

    printf("JNI_OnLoad\n");
	// 动态注册JNI函数
    if(register_jni_function(env) < 0) {
        printf("fail to register jni function");
        return -1;
    }
    return JNI_VERSION_1_4;
}

java层在调用System.loadLibrary()加载动态库后,java虚拟机就会去该库中寻找JNI_OnLoad这个函数,如果有则会调用它,我们的动态注册也就是在这里完成的,所以如果想实现动态注册,就必须实现JNI_OnLoad这个函数。

jint: JNI层,整型的标示方法
JavaVM:用来描述java虚拟机的数据类型
JNIEnv: 用来描述Jni层代码运行的环境,其实这个结构体中包含了很多函数指针,通过这些函数指针我们可以操作JAVA层的对象,完成动态注册。

例如:创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等等。
在这里插入图片描述
JAVA层的native函数在JNI层实现的时候,总是会带两个参数,一个就是JNIEnv指针,另一个是object表示Java层的对象。

Test.java

class Test {
	//通过"native"修饰成员函数接口,表示这个函数接口是由C/C++来实现的
    public native void sayHello();

    static {
        System.out.println("start load static lib");
        //通过System.loadLibrary()加载对应的动态库
        System.loadLibrary("helloworld");
    }

    public static void main(String[] args) {
        
        Test mObj = new Test();
        mObj.sayHello();      
    }
}

Test.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Test */

#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Test_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

helloworld.c

#include <jni.h>
#include "Test.h"
#include <stdio.h>

int jni_sayHello(JNIEnv *env, jobject obj) {
    printf("hello world!!!\n");
    return 0;
}

static JNINativeMethod gMethods[] = {
    {
        "sayHello",
        "()V",
        (void *)jni_sayHello
    },
};

int register_jni_function(JNIEnv *env) {
    jclass clazz;
	//通过JNIEnv获取Test类
    clazz = (*env)->FindClass(env, "Test");
    if(clazz == NULL) {
        return -1;
    }
    printf("Findclass\n");
    //调用JNIEnv的RegisterNatives函数完成注册
    if((*env)->RegisterNatives(env, clazz, gMethods, 1) < 0) {
        return -1;
    }
    printf("RegisterNatives\n");
    return 0;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        printf("JavaVm fail to get JNIEnv\n");
        return -1;
    }

    printf("JNI_OnLoad\n");

    if(register_jni_function(env) < 0) {
        printf("fail to register jni function");
        return -1;
    }
    return JNI_VERSION_1_4;
}

为啥要先知道类呢?
java是通过加载类来运行的,由于Java虚拟机中有多个类,每个类可能有native方法,所以在注册时候,就必须告诉java虚拟机,我们注册的是哪一类的native方法。

三.生成动态库,运行JAVA程序

1.生成动态库
gcc -fpic -shared -o libhelloworld.so helloworld.c -I /usr/lib/jvm/java-8-openjdk-amd64/include/ -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/

其中-I 指定jni.h文件所在的路径

2.运行java程序
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值