java 通过jni调用linux so动态库

  • java 通过jni调用linux so动态库
  • 准备
  • 开发
  • java
  • C++
  • 总结


java 通过jni调用linux so动态库

欢迎转载 地址:

最近有个项目需要java调用C++的动态库,所以重温一下操作步骤记录一下。

准备

使用环境intellij idea clion 系统环境centos:
平时开发使用开发环境是windows 所以部署到linux 上面 调试起来比较麻烦
所以开发jni调试还是挺麻烦的,毕竟开发环境和部署环境不一样

1.下载linux版本的jdk,linux上面也要安装jdk环境,不要忘记这一步;
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2. clion需要环境是MinGW,所有下载一个MinGW;
3. linux上面安装 gcc , gcc-c++
yum install gcc , gcc-c++ -y 4. 把linux版本的jdk里面文件夹include的 jni.h , jawt.h 和 linux文件夹里面的 jni_md.h , jawt_md.h 复制到 MinGW 的include里面,顺便把这4个文件放在 gcc安装文件夹 include里面;

开发

环境配置的差不多了,接下来就是代码部分

首先第一部分就是java代码

java

package com.ruan.jni;

public class Jni {

    static {
    	//这个加载绝对路径动态库
        System.load("/opt/cpp/com_ruan_jni_Jni.so");
    }
    //实现以下两个原生方法
    public native int add(int a , int b);

    public native String print(String msg);
}

编译将java 生成class文件
之后将class放在linux环境上,将class文件生成需要用到的.h头文件
现在这个class的包名名是 com.ruan.jni
所以在linux的 根目录创建 这3级目录

cd /
mkdir com
cd com
mkdir ruan
cd ruan
mkdir jni

之后将class文件放在/com/ruan/jni文件夹下面
下一步就是javah生成.h头文件

javah -classpath A -d B -jni C

A: com/ruan/jni这三级文件夹的目录 我们这里使用的是根目录所以是 /
B:输出文件的目录
C:需要编译的class文件,这个重点就是一定要加上包名 com.ruan.jni.Jni 后面的class后缀不需要

javah -classpath / -d / -jni com.ruan.jni.Jni

之后就会在根目录 生成 一个 名称为 com_ruan_jni_Jni.h头文件

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

#ifndef _Included_com_ruan_jni_Jni
#define _Included_com_ruan_jni_Jni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ruan_jni_Jni
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_ruan_jni_Jni_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_ruan_jni_Jni
 * Method:    print
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ruan_jni_Jni_print
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

上面就是生成的头文件

头文件生成之后就是编写实现的cpp文件

第二部分 编写C++代码

C++

cpp文件名称和.h文件名称一样
com_ruan_jni_Jni.cpp

#include <jni.h>
#include <iostream>
#include <com_ruan_jni_Jni.h>
#include <string>


using namespace std;

JNIEXPORT jint JNICALL Java_com_ruan_jni_Jni_add(JNIEnv *env, jobject job, jint a, jint b) {
    jint c;
    c = a + b;
    return c;
}


JNIEXPORT jstring JNICALL Java_com_ruan_jni_Jni_print(JNIEnv *env, jobject job, jstring s){
//    const char *buf = env->GetStringUTFChars(s , NULL);

    char str[] = "欢迎你的到来!";

    //字符串拼接,实现strContent+str1,因为strcat的第一个参数必须为非const类型(可变),所以不能直接使用strcat()
    //创建一个新的字符串指针
//    char *strTemp = (char *) malloc(strlen(buf) + strlen(str) + 1);
    //拷贝常量到字符串指针
//    strcpy(strTemp,buf);
    //拼接str1到strTemp
//    strcat(strTemp,str);
    std::string hello = "Hello from C++";

    printf(str);

    return env->NewStringUTF(hello.c_str());

//    return env->NewStringUTF(str);
}

上面代码就是实现原生方法的具体实现方式 紧做参考

现在源码和头文件都有了,接下来就是生成动态库so文件
之后将cpp和.h文件在linux环境生成so文件

g++ -fPIC -shared -o A B

A:生成动态库的名称
B:生成动态库需要用到的文件 我们这里使用的 com_ruan_jni_Jni.cpp和com_ruan_jni_Jni.h

g++ -fPIC -shared -o com_ruan_jni_Jni.so com_ruan_jni_Jni*

后面之所以加入* 因为要加入多个文件

package com.ruan.jni;

public class Main {
    public static void main(String[] args) {
        System.out.println(new Jni().print("测试打印"));
        System.out.println(new Jni().add(1 , 2));
    }
}

运行上面的方法就可以输出打印
不过唯一注意 这个要打包到linux环境上面进行 运行

java -jar xxx.jar

否则会报错

总结

上面就是简单的 java 调用C++方式,这个是居于java jni生成的头文件进行编写的so,
但是如果遇到so不是按照jni生成头文件提供的接口,那么这种方式 显然只是完成一部分而已,还不能直接使用最原生的C/C++动态库,那么最直接的方法,就是在原生的so上面进行封装一层 按照jni格式的 so 让java来调用
这种方法 后续有时间再记录一下,今天到这里