1、介绍

      本文将记录一下java jni调用so动态库的方法,其实最开始我的想法是这样:我自己手里有一个别人的so库,然后我需要用java将起调用起来,但是经过我查找的资料发现,所有的方法都是用jni和c语言的源码一起编译生成so库的过程,并没有找到相关jni直接可以调用现成的so库的资料,最后经过我对gcc编译的经验,最后还是把so库调用起来了,那么,我们接下来将介绍我的调用方法,以下的验证测试我是在centos上面测试的。

2、java使用jni生成so库

    这个过程是基础过程,就是网上大部分的教程,我也一并记录一下,jni生成静态库的过程,前提是你安装有java的编译环境。

首先实现一个简单的jni调用,写一个简单的java代码,我们命名为hello.java,代码如下:

//hello.java
package sf.gg;
public class Hello{

public native static int testjni(int x,int y);

static {
        System.load("/home/test/test_sdk1.so");//动态库so库的绝对路径,相对路径没试过,不知道行不行
        }
        public static void main(String[] args){
                System.out.println(testjni(1,1));

        }

}

然后就在hello.java文件目录下执行编译命令#javac -d . hello.java

-d参数是表示生成目录,.表示在当前目录生成目录

执行命令后使用ls命令可以看到当前目录下面多了个名字为sf的文件夹

下一步就是c代码部分:

首先执行命令生成c代码的.h头文件

#javah sf.gg.hello

命令执行后在当前目录下面多了一个sf_gg_hello.h的头文件,里面的内容大概如下所示:

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

#ifndef _Included_sf_gg_hello
#define _Included_sf_gg_hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     sf_gg_hello
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_sf_gg_hello_testjni
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
~                                                                                                   
~

然后手动自己生成头文件对应的.c源文件,我们命名为hello.c,内容大致如下:

#include<stdio.h>
#include "sf_gg_hello.h"

JNIEXPORT jint JNICALL Java_sf_gg_hello_testjni(JNIEnv *env, jclass jc, jint x, jint y){
        int res = 0;
        printf("hello world this is a test!\n");
        return x+y;
}

其中头文件和源文件中的Java_sf_gg_hello_testjni函数对应就是hello.java中的testjni函数的实现,java文件中调用testjni,就是调用Java_sf_gg_hello_testjni函数,该函数到时候会封装的test_sdk1.so库文件中,然后java进行调用。

在命令行执行如下命令,该命令是将hello.c文件编译成目标文件hello.o:

#gcc -fPIC -D_REENTRANT -I /usr/lib/jvm/java-1.8.0/include/ -I /usr/lib/jvm/java-1.8.0/include/linux/ -c hello.c

每个Linux的java环境可能不同,所以/usr/lib/jvm/java-1.8.0/include/不是绝对的,需要换成你自己Linux系统java的jdk的目录

最后再用gcc将hello.o文件编译成so库即可使用

#gcc hello.o -o test_sdk1.so -shared

最后就会在当前目录下面生成test_sdk1.so动态库文件

最后运行命令#java sf.gg.hello即可以完成java对jni的调用

3、调用第三方的so库怎么调用

现在关键的步骤来了,基本上网络上的方法都是上面描述的方法,都是通过源码开发生成so库,然后java来调用,但是对于没有源码,只有so库和头文件的怎么jni调用呢,那么我们接着来。

假如说你有个第三方的so库,名字为libtest_sdk.so文件,头文件为test_sdk.h,头文件里面有个函数是test_sdk_api(void),此时怎么能够将jni调用起来呢。首先申明一下,该函数只能在hello.c文件里面进行调用,无法在hello.java里面调用,hello.c可以看成是一个jni调用的中间件。

调用方法如下:

首先考呗test_sdk.h,和libtest_sdk.so库文件到hello.c所在目录,然后修改hello.c文件为如下:

#include<stdio.h>
#include "sf_gg_hello.h"
#include "test_sdk.h"    //包含外部第三方动态库头文件

JNIEXPORT jint JNICALL Java_sf_gg_hello_testjni(JNIEnv *env, jclass jc, jint x, jint y){
        int res = 0;
        test_sdk_api();    //调用第三方so库文件函数
        printf("hello world this is a test!\n");
        return x+y;
}

然后再重新生成hello.o文件,命令如下(和之前的一样):

#gcc -fPIC -D_REENTRANT -I /usr/lib/jvm/java-1.8.0/include/ -I /usr/lib/jvm/java-1.8.0/include/linux/ -c hello.c

然后再生成test_sdk1.so库文件,这一步骤比较关键,和前面的不一样,这个时候需要链接第三方的libtest_sdk.so库,命令如下所示:

#gcc hello.o -ltest_sdk  -o libtest_sdk1.so -shared

其中参数 -ltest_sdk就表示链接第三方动态库test_sdk.so

最后生成libtest_sdk1.so

最后执行命令#java sf.gg.hello即可以完成java对test_sdk.so库文件的test_sdk_api()函数的jni的调用

如果test_sdk_api()函数里面有打印信息,就可以看到相应的打印信息。

备注:在调用的时候,libtest_sdk1.so和libtest_sdk.so两个库文件都必须同时存在才行