本文是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结构体参数【二】