JNI入门与进阶,JNI调用外部非标准程序SO【一】

本文是JNI专题的开头文章,主要介绍JNI项目如何从零到整的过程

一、JNI简介

Java Native Interface,即Java本地接口。
它允许在Java虚拟机内运行的java代码与其他编程语言(如c, c++和汇编语言)编写的程序和库进行交互。简单点说, JNI可以帮助我们用java代码访问其他编程语言

二、JNI项目

需求:开发 JAVA WEB服务调用已有的so动态链接库,完成相关的业务功能开发。
因此需要借助JNI技术来完成。标准JNI的结构如下
在这里插入图片描述
java代码借助JNI调用由C代码生成的动态链接库文件(SO文件),从而访问C程序的函数,最后得到函数执行的结果。
但是这套东西要求C语言的函数命名要符合JNI的规则才能被JAVA识别。所以经过分析与预研,最后的结构变成
在这里插入图片描述
引入自定义的C编程,以及自定义的标准动态链接库。然后再用自定义的C去调用外部的非标准C程序。
因此,项目的JNI编程既包括java程序的开发,还包括C语言程序的开发。

技术要点:

1、JNI编程相关
2、C语言调用动态链接库
3、动态链接库生成

三、JNI编程

JNI编程目的:JAVA调用C的动态链接库函数
JNI例子的目标:调用Cadd方法完成两数相加计算,返回结果;打印一串子串并返回

1、编写JAVA程序

/**
 * HelloJNI
 */
public class HelloJNI {

    static {
        // 加载动态链接库
        System.load("/program/jni/libHelloJNI.so");
    }

    /**
     * 声明两个Native Method,对应C的两个方法
     */
    public static native int add(int a, int b);
    public static native void hello();

    public static void main(String[] args) {
        // 调用 add方法,完成计算
        int sum = add(5, 6);
        System.out.println("sum=" + sum);
        // 调用 hello方法,在c语言内完成打印
        hello();
    }
}

说明:
Native Method就是一个java调用非java代码的接口,该方法的实现由非java语言实现,比如C。也就是说java只写接口代码,不写实现。实现在C程序


2、编译java
①上传HelloJNI.java到linux环境的/program/jni目录
②执行命令编译

javac -encoding utf-8  HelloJNI.java

③执行命令生成JNI头文件文件

javah -jni HelloJNI

说明:
(1)必须先编译完java才能执行,因为是建立在.class文件之上
(2)命令中HelloJNI表示类名,如果该类有package,要加上完整的包名,然后javah 命令一定要在包路径的上层。比如 HelloJNI的完整类名是com.test.jni.HelloJNI,那么HelloJNI.class文件需要放在
/program/jni/com/test/jni目录下,然后到/program/jni目录执行命令javah -jni HelloJNI
(3)现在的例子没有package,执行完,现在目录有三个文件
在这里插入图片描述


3、开发c
首先我们拿到上面生成的HelloJNI.h文件,里面的内容如下:

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

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

/*
 * Class:     HelloJNI
 * Method:    hello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_hello
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

新建HelloJNI.c,内容如下

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include "HelloJNI.h"

/*
 * Class:     HelloJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_HelloJNI_add(JNIEnv *env, jclass jc, jint a, jint b){
  return a + b;
}

/*
 * Class:     HelloJNI
 * Method:    hello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT void JNICALL Java_HelloJNI_hello (JNIEnv *env, jclass jc){
    printf("%s\n", "hello world!");
}

说明:
①加上#include “HelloJNI.h”,就是把之前生成的头文件引入
②在HelloJNI.h的基础上写上函数体
③需要去掉HelloJNI.h多余的注解,然后补全函数变量和函数体


4、编译C文件
/program/jni目录下执行命令

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libHelloJNI.so HelloJNI.c

至此,目录/program/jni下的文件有
在这里插入图片描述

5、运行程序
在/program/jni目录下执行命令

java HelloJNI

运行结果如下,符合预期
在这里插入图片描述

6、命令汇总
编译java: javac HelloJNI.java
生成Native Method函数映射头文件: javah -jni HelloJNI
生成c文件的动态链接库: gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libHelloJNI.so HelloJNI.c
运行java程序: java HelloJNI

至此,已经完成标准JNI的整个开发
即完成 HelloJNI.java–>HelloJNI.c的部分开发与验证

四、非标准SO调用

目标:实现HelloJNI.java–>HelloJNI.c–>hello.c的开发与验证
其中hello.c是非标准JNI的C程序
回过头看看什么事标准JNI的C程序,有配套的头文件HelloJNI.h,并且就是函数命名如

JNIEXPORT jint JNICALL Java_HelloJNI_add(JNIEnv *env, jclass jc, jint a, jint b)

简单扥说 JNIEXPORT jint JNICALL 形式表示java native映射并调用的声明,JNIEnv ,jclass ,jint 等是JNI的变量类型,基本类型对应如下,更多的对应可以去=去网上查
在这里插入图片描述
现在有非JNI标准c程序hello.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * 返回a+b的值
 */
int add(int a, int b){
    return a + b;
}

现在的需求就变成我们要用java调用这个函数,下面开始!


1、生成hello.c的so
将hello.c放到/program/jni目录,在该目录下执行命令

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libhello.so hello.c

此时会生成文件libhello.so


2、标准c开发
我们把之前的HelloJNI.c进行改造

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include "dlfcn.h"
#include "HelloJNI.h"

/*
 * Class:     HelloJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_HelloJNI_add(JNIEnv *env, jclass jc, jint a, jint b){

    // 加载连接库
    void *handle = dlopen("/program/jni/libhello.so", RTLD_LAZY);
    if (handle == NULL) {
        return -1;
    }
    // 定义调用的函数
    int (*doFunc)(int , int) = NULL;
    // 声明执行函数
    doFunc = (int (*)(int, int)) dlsym(handle, "add");
    // 执行调用
    int result = doFunc(a, b);

}

/*
 * Class:     HelloJNI
 * Method:    hello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT void JNICALL Java_HelloJNI_hello (JNIEnv *env, jclass jc){
    printf("%s\n", "hello world!");
}

说明:
①引入 #include “dlfcn.h” 因为底下加载外部链接库用到一些关键字
②用dlopen加载非标准的动态链接库

3、运行程序
因为其他的文件用之前的,没变,所以只需要执行

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libHelloJNI.so HelloJNI.c;
java HelloJNI;

运行结果
在这里插入图片描述
最终的文件列表如下
在这里插入图片描述

五、总结

至此,完成了整个调用链,整个文章,最终追求的效果如下
在这里插入图片描述

.
.
.
.
.下 一 篇.:JNI对象传参与返回对象结果-C结构体参数【二】

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cy谭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值