作为一个非专业的专业开发者,笨(不系统)是天性,积累是习惯,一个小小的机箱内的东西对个人来说,像宇宙一样不简单。痛苦也好,辛苦也罢,在这个局促的机箱中遨游吧。扯远了哈,个人觉得JNI是java语言与系统交互的基础,如I/O处理,对象创建等都离不开JNI的实现。因此,笔者决定以此作为学习JDK的初篇,慢慢去揭开java语言的面纱,没准哪天笔者自己写一个Eava或者Hava也说不定,期待吧,比卡丘!
一、基本介绍
JNI是Java Native Interface的缩写,即java本地接口(来源:度娘)。
我们知道Windows系统也好(本文章下文做的操作皆以此系统作为运行环境),Linux系统也罢,汇编语言只在系统的开发中占一小部分,主要应用在与机器交互的内核命令中,其中一大部分还是用c/c++开发的,特别是系统提供给开发者和用户的库函数中。因此,一些我们比较熟悉的跨平台语言如python、java、golang等(这个排序不代表笔者心中的地位,笔者一直认为机器语言是最好的语言,一切尽在0|1中)底层都是用c/c++开发的。
或者我们也可以这样理解,高级语言只是一种表达方式,比如一块石头,你说“stone”还是“石头”,最终想要给别人展示的都是那块玩意儿,我们只不过是用不同的语言体系赋予了一个物什抽象表达,而JNI则是java语言体系对于本地函数(库函数、自定义c/c++函数)的转换方式罢了。
二、静态链接和动态链接
一个c语言项目需要经过预处理、编译、汇编、链接阶段才能生成可执行文件,经过汇编之后就生成了包括我们机器认识的机器指令的对象文件。对象文件不是可直接执行的文件,特别是,当我们在一个对象文件中还调用了别的文件的函数时,就需要通过链接的方式,让两个文件生成调用与被调用关系。在c语言中链接的方式有两种,即静态链接和动态链接。
2.1 静态链接
静态链接很像是结了婚,别小看那个小本本,它在让两个人都有了家的存在的同时,定义了一个法定事实,即你的是我的,我的还是我的,在这个过程中调用和被调用函数都被放进了可执行文件中,因此,可执行文件脱离了静态库也是可以运行的。我们来看一个示例:
add.c
int add(int a,int b)
{
return a + b;
}
multi.c
int multi(int a,int b)
{
return a * b;
}
calc.h
int add(int a, int b);
int multi(int a,int b);
步骤如下:
1)新建文件夹 lib,在文件夹中创建并编辑上面三个文件
2)将.c文件生成静态链接库需要借助编译器,可以下载安装mingW或其他的编译器,作者是用的mingW,下载安装的过程作者就略述了。
3)在lib文件夹下打开DOS,生成静态链接库
#将上面编辑的.c文件在命令行中编译成.o文件(对象文件)
gcc -c *.c
#生成静态链接库
ar rcs wife.a *.o
此时,我的静态链接库就生成了,可以通过命令 ar -t wife.a 查看到库中包含了 add.o 和 multi.o 两个文件。这时候我们重新创建
4)新创建一个文件夹home,把 calc.h 和 wife.a 文件都拿过来,创建并编辑 main.c 文件
#include <stdio.h>
#include <stdlib.h>
#include "calc.h"
int main()
{
int a = 2;
int b = 5;
int c = add(a, b);
int d = multi(a, b);
printf("the result of %d + %d is %d\n", a, b, c);
printf("the result of %d * %d is %d\n", a, b, d);
return 0;
}
5)在 home 文件夹下运行DOS并执行下面的命令,就能生成可执行文件main.exe,这个main.exe文件是可以脱离wife.a直接执行的。
gcc main.c wife.a -o main
2.2 动态链接
动态链接像情人关系多一点,链接库是独立存在的,并不会被包含在可执行文件中,而且,它不一定只是一个调用者的情人,也可以同时是很多调用者的情人。通过动态链接生成的可执行文件,不可脱离链接库运行,关系不牢靠。
生成动态链接只需要对上面生成静态链接的命令做一下修改就行:
gcc -shared -o lover.dll *.o
创建hotel文件夹,把main.c、calc.h 和 lover.dll 都放在该文件夹下,在 hotel文件夹下运行DOS并执行下面的命令,就能生成可执行文件main.exe,如果我们此时把lover.dll,发现main.exe是无法运行的。
gcc main.c lover.dll -o main
三、java和动态链接
通过第二节的介绍,我们对动态链接应该也有了一定的了解,java中的native方法正是通过JNI调用动态链接库实现对自定义c函数的调用的。话不多说,直接上步骤吧:
1)重新创建一个jni文件夹在里面编辑我们本地方法的java类
/**
* @auther Elean
* @date 22/7/2024 下午11:29
* @description
*/
public class Demo {
public native String myMachine();
}
2)在文件夹下打开DOS,通过命令行生成对应.c文件的头文件
javac -h .\ Demo.java
打开头文件,可以看到其内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Demo */
#ifndef _Included_Demo
#define _Included_Demo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Demo
* Method: myMachine
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Demo_myMachine
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
注:关键字解释
JNIEXPORT:在JNI中定义的宏,相当于java中的访问修饰符,可以不关注
JNICALL:与JNIEXPORT类似,也是在JNI中定义的宏,但目前无实际意义
jstring:与java中的String相对应,是在jni中定义的一种引用类型,类型对应关系感兴趣的话可以自行查资料。
定义的宏:
#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility)
#define JNIEXPORT __attribute__((visibility("default")))
#define JNIIMPORT __attribute__((visibility("default")))
#else
#define JNIEXPORT
#define JNIIMPORT
#endif
#define JNICALL
3)根据头文件,编写我们自己的.cpp或.c文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Demo */
#ifndef _Included_Demo
#define _Included_Demo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Demo
* Method: myMachine
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Demo_myMachine(JNIEnv *env, jobject obj)
{
char* str = "this is elean's machine";
return env->NewStringUTF(str);
}
#ifdef __cplusplus
}
#endif
#endif
4)生成动态链接库
-I:大写i,生成动态链接时需要引入JDK中include下的jni.h和include/win32下的jawt_md.h、jni_md.h
gcc -shared -o lover.dll -I "E:\soft\jdk1.8.0_271\include" -I "E:\soft\jdk1.8.0_271\include\win32" Demo.cpp
5)重新编辑java代码,通过javac编译成.class文件。
public class Demo {
public native String myMachine();
static{
//通过绝对路径加载动态链接库
System.load("C:\\Users\\lenovo\\Desktop\\jni\\" + "lover.dll");
}
public static void main(String[] args) {
String machine = new Demo().myMachine();
System.out.println(machine);
}
}
打印结果:
~jni>java -cp D:\jni Demo
this is elean's machine
本文中只是简单的介绍了java通过JNI调用本地方法的实现方式。在实现过程中,笔者是直接使用mingW编译器通过命令行的方式实现的编译和执行,读者们也可以下载c/c++语言的IDE工具,在工具里面进行操作,笔者也很流畅的尝试了用codeblock进行编译的方式,希望大家也多多去实操一下,如果遇到什么问题,在留言里面互动促进相互学习。
那么,JNI究竟如何对c语言进行包装才变成我们熟悉的java语系的呢?由于时间原因(主要笔者还没研究),就留待下篇文章唠吧,敬请期待!!!