Java底层-本地接口(JNI)
前面的文章我们讲述了类加载子系统、执行引擎、GC子系统、运行数据区的相关内容,那么HotSpot虚拟机三大子系统、两组件就剩下本地接口组件没有讲述了,所以这一篇文章对于本地接口进行介绍。
为什么会有本地接口的概念呢?我们在之前的文章说过Java语言不是面向硬件的,它无法直接调用操作系统API操控硬件,Java和硬件的交互正常都是通过JVM提供的API来完成的,但是当虚拟机提供的API不足以实现我们个别需求的时候,就需要本地接口了。由于JVM底层就有C++的影子,所以JVM也提供了JNI(Java本地接口:Java Native Interface,)技术作为其它语言(主要是C/C++)通信的API。在本地接口的相关的概念里,Java成为了一个调用方,其他语言成为了主角,这篇文章我们以C++为例,看一下如何调用本地接口,关于C++的部分尽可能简单的描述,如果大家还有疑问可以了解一些C++的基本知识。
首先编写调用本地方法的类,方法定义为native,表明调用的是本地接口。
package com.studyjava.email.jni.main;
public class Main {
//加载Library,将Main资源库加载到内存中
static {
System.loadLibrary("Main");
}
public native void studyJava();
public static void main(String[] args) {
new Main().studyJava();
}
}
定义完native方法之后,就需要我们使用C++去实现本地接口供Java使用,但是Java调用的本地接口并不是随意一个本地方法就可以调用的,这些本地接口定义是存在一些规律的,如果我们不知道这些规律可以通过javah命令生成,javah命令可以将java文件生成一个头文件(.h后缀的文件),这种转换会将native方法抽离到头文件中,我们可以认为头文件很像一个接口,Java可以通过头文件来调用资源的库程序,在一些特殊场合下,C/C++的源代码如果不便(或不准)向用户公布,那么只要向用户提供头文件和二进制的库即可。
javah -d jni com.studyjava.email.jni.main.Main
当然我们可以不使用javah命令,这里也可以自己去写.h文件,但是要保证格式是正确的,如下代码。
注:代码include就类似我们Java的import一样,
Java_com_studyjava_email_jni_main_Main_studyJava就是我们要实现的方法
#include <jni.h>
/* Header for class com_studyjava_email_jni_main_Main */
#ifndef _Included_com_studyjava_email_jni_main_Main
#define _Included_com_studyjava_email_jni_main_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_studyjava_email_jni_main_Main
* Method: studyJava
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_studyjava_email_jni_main_Main_studyJava
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
当头文件有了之后,我们我们就需要编写它的实现,首先定义.cpp文件(可以使用C++开发工具或者纯文本编写也是可以的), cpp文件中首先引入我们生成的头文件,引入头文件之后,我们就对上面头文件的方法 JNIEXPORT void JNICALL Java_com_studyjava_email_jni_main_Main_studyJava 进行实现,这里 使用print函数(或者cout)打印了"每天学Java"。
#include <jni.h>
#include <iostream>
#include "com_studyjava_email_jni_main_Main.h"
JNIEXPORT void JNICALLJava_com_studyjava_email_jni_main_Main_studyJava(JNIEnv *, jobject){
printf("每天学Java");
return;
}
int main(){
return 1;
}
编写.cpp文件就类似我们写接口的实现类,当我们编写完之后,需要将cpp文件编译成.jnilib文件(我这里是Mac操作系统,如果是Windows需要转为.dll文件,Linux/Unix则为.so文件,这个命令的具体含义放在文末)。
g++ -shared -I /Users/.../include/Users/.../include/main.cpp-o libMain.jnilib
如果大家没有C++环境可以百度部署下,MacOS中如果安装XCode,会自带C++环境。
有C++环境之后,编译过程中可能会出现下面报错,
'jni.h' file not found
这是因为我们引入的jni.h找不到,我们可以将jdk路径下的include复制出来,和上面编译的文件同级, 如下图,其中include下jni_md是从darwin中复制到include目录下,因为jni.h中存在对jni_md.h的引用,所以为了方便放到同级目录下
其中lib中存放的就是libMain.jnilib,也就是我们编译的本地资源库。到这里我们本地接口就算编写完成了,调用的时候需要指定Library。
-Djava.library.path=/Users/.../java/lib
否则会有如下报错:
java.lang.UnsatisfiedLinkError: no XXX in java.library.path
关于g++命令的参数这里简单的说下 :
-shared是说明要生成动态库。
-I的选项,是因为我们用到<jni.h>相关的头文件,放在<jdk>/include目录下。最
-o 选项,我们在java代码中调用的是System.loadLibrary("Main"),那么生成的动态链接库的名称就必须是libMain.jnilib的形式,否则在执行java代码的时候,同样会报 java.lang.UnsatisfiedLinkError: no XXX in java.library.path 的错误
头文件具体用处:
(1)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
关于本地接口到这里就讲完了,这篇文章只讲述了我们如何去调用本地接口,但是原理并没有提及,大家可以追踪System.loadLibrary()方法进行学习。
- 游戏服务器之内存数据库redis客户端应用(下)
- 使用Python和Tesseract来识别图形验证码
- 高并发服务器的设计--连接池的设计
- Pwnable.tw刷题之Silverbullet破解过程分享
- 鸡肋CSRF和Self-XSS组合的变废为宝
- 简易--go语言redis连接池
- Golang中channel使用的一些小技巧
- Go语言的管道Channel用法实例
- 【数据库】MySQL进阶二、索引简易教程
- 【数据库】MySQL进阶六、模糊查询用法
- 【选择题】Java基础测试九(16道)
- 用Ansible自动供应vmware虚拟机--构建数据中心一体化运维平台第二篇
- 【编程题】Java编程题五(10道)
- 如何有效地对Docker的镜像进行管理?
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 【Vue.js】Vue.js组件库Element中的弹出框、气泡确认框、卡片和走马灯
- 【Vue.js】Vue.js组件库Element中的折叠面板、时间线、分割线和日历
- 从Properties乱码来学习编码
- 【Vue.js】Vue.js组件库Element中的图片、回到顶部、无限滚动和抽屉
- Java逐行读取和写入文件
- Vuejs使用v-for指令实现九九乘法表
- Cypress系列(43)- visit() 命令详解
- 在GitLab pages上快速搭建Jekyll博客
- Dubbo项目中No provider available for the service xxx from registry xxx on the consumer问题的解决思路
- Mysql面对高并发修改的问题处理【2】
- java (多网卡环境下)发送组播广播(multicast/broadcast)失败问题
- activmq:android平台下使用openwire协议连接activemq服务的问题
- Leetcode No.9 回文数
- go-zero微服务框架入门教程
- 聊聊java中的哪些Map:(九)TreeMap源码分析