用CLion实现本地方法并给java调用
众所周知,PHP是世界上最好的语言,java排第二,因为PHP无所不能。但是在某些场景下java还要调用本地方法来提高执行的效率,故java只能排第二。java提供了jni(Java Native Interface)来实现在java中调用本地方法。本地方法在java中用native关键字标识,它是一种和机器有关的方法,一般用C或C++实现,而本地方法不是跨平台的,不同的平台需要重新编译。jdk中就有不少地方用了native方法,比如Object类中的hashCode方法:
public native int hashCode();
下面开始使用jni:
(一)创建一个带有native方法的类
package com.example.jni;
public class JNIObject {
private String name;
public JNIObject(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int add(int param1, int param2) {
return param1 + param2;
}
public int sub(int param1, int param2) {
return param1 - param2;
}
public native int multi(int param1, int param2);
public native int div(int param1, int param2);
}
我们假定加减法执行效率高可以直接用java实现,而乘除法比较慢,需要用C语言来实现。写好了类我们先编译,把java文件编译成class文件,然后再用javah命令生成c头文件,执行javah命令时要注意,我们需要先把当前的工作目录切换到class所在的根目录,就是包的第一级目录所在的目录。比如我们的包名是com.example.jni,那么我们需要切换到com目录所在的目录,执行的命令格式是javah [-option] 包名.类名
javah -jni com.example.jni.JNIObject
成功后会在当前的工作目录生成一个.h的文件(com_example_jni_JNIObject.h),到此我们就得到了本地方法的接口了,如果有c程序员,可以让他们实现,否则看第2步。
(二)在CLion中实现native方法
如果没有CLion先请自行安装。
- 创建一个C Library项目,填好路径和项目名,Library type选择shared,Language standard是指c语言的不同标准,类似于我们的jdk的版本,如果你不是c程序员,直接用默认的C99标准就好了
- 设置编译环境
如果你使用过Visual Studio的话,你可能安装之后直接创建项目就能写代码了,但是CLion有点不同,它只是一个开发集成环境,只提供了构建工具cmake,并没有提供编译器(Visual Studio全套都提供好了),这可以让开发者自由去选择自己喜欢的编译器,我们打开File->Settings,并选中Build,Execution,Deployment下的Toolchains
可以看到CLion列出了两个常用的编译器MinGW和Cygwin,这里我们使用MinGW作为我们的编译器。需要注意的是如果你的jdk是64位的,那么也要选择64位的MinGW,不然在调用的时候会出错,下面是windows下的MinGW64位下载地址
http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download
下载后直接安装就行了,安装成功后在上图中的Environment选择MinGW的home目录,然后下面的C、C++编译器还有调试器都会根据选择的目录自动找到相关工具,然后点OK就完成了我们的编译器的设置。
- 实现native方法
我们先把第一步生成的c头文件(com_example_jni_JNIObject.h)复制到CLion项目中,这时在打开的com_example_jni_JNIObject.h文件顶部出现一行提示:This file dose not belong to any project target, code insight features might not work properly. 这时我们打开CMakeLists.txt文件,在add_library中加入我们的头文件,完成后点击提示的reload changes,完成后的CMakeLists.txt的内容如下:
cmake_minimum_required(VERSION 3.15)
project(jni C)
set(CMAKE_C_STANDARD 99)
add_library(jni SHARED library.c library.h com_example_jni_JNIObject.h)
再切换到com_example_jni_JNIObject.h可以发现刚才的警告已经消失了,但是第二行的#include <jni.h>报错了,这时因为MinGW编译器没有jni.h这个头文件,打开JDK的home目录,在include目录中可以找到jni.h头文件,除此之外,我们还需要include/win32目录下的jni_md.h头文件,一共两个,把这两个头文件都复制到MinGW安装目录(就是设置编译环境时的那个Environment的值)下的x86_64-w64-mingw32中的include目录中,注意这两个头文件是一起放在MinGW的这个目录的,jni_md.h不需要另外创建一个win32目录来存放。完成后发现com_example_jni_JNIObject.h的报错消失了。
我们右键点击项目,选择New->C/C++ Source File,然后创建一个源码文件,type选择.c,如果你习惯使用C++就选.cpp
点击OK完成,下面是具体的实现
#include "com_example_jni_JNIObject.h"
JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_multi
(JNIEnv *env, jobject o, jint param1, jint param2) {
return param1 * param2;
}
JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_div
(JNIEnv *env, jobject o, jint param1, jint param2) {
return param1 / param2;
}
解释一下上面的源文件,#include是把后面的com_example_jni_JNIObject.h头文件包含进来,和java的import作用类似,下面的两个方法就是在这个头文件中声明的函数,c语言在声明函数时可以忽略参数名,只写参数类型,但是现在我们是在实现函数,所以必须加上参数名。如果是学习过c语言的很容易理解,没学过的了解一下就好。
- 编译动态链接库
到此,我们的代码就完成了,点击菜单栏的Build->Build Project成功后在在左侧的项目结构里生成了一些文件,其中cmake-build-debug目录下的libjni.dll就是我们需要的动态链接库了,如果是linux系统,生成的是.so格式的文件。
(3)在java中调用native方法
回到java目录,我们在项目的根目录下创建一个jni目录,把我们的dll文件复制进去,复制好之后会自动打开,发现是乱码,因为dll文件是二进制格式的,我们直接关掉。
在JNIObject类中添加一个静态代码块,用来加载我们的动态链接库,完成后的JNIObject类如下:
public class JNIObject {
static {
System.loadLibrary("libjni");
}
private String name;
public JNIObject(String name) {
this.name = name;
}
...
}
注意loadLibrary方法不用写dll后缀名。
我们新建一个测试类Main,代码如下:
package com.example.main;
import com.example.jni.JNIObject;
public class Main {
public static void main(String[] args) {
JNIObject jniObject = new JNIObject("jni");
System.out.println(jniObject.getName()); // 调用java方法
System.out.println(jniObject.add(1, 2)); // 调用java方法
System.out.println(jniObject.sub(1, 2)); // 调用java方法
System.out.println(jniObject.multi(2, 3)); // 调用native方法
System.out.println(jniObject.div(6, 2)); // 调用native方法
}
}
我们先运行一个Main类的main方法,发现报错了:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libjni in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at com.example.jni.JNIObject.<clinit>(JNIObject.java:6)
at com.example.main.Main.main(Main.java:8)
这是因为我们还没有指定jni库的加载路径,导致loadLibrary方法无法找到我们的dll库。点开运行按钮下拉菜单的Edit Configurations,我们给Main类加一个启动参数-Djava.library.path,这个参数就是异常信息出现的参数,指定值为jni目录
重新运行main方法,可以看到已经可以正常执行native方法了。
总结
总结一下jni的调用过程,先定义好native方法,然后通过javah生成头文件,然后用C或C++实现函数,编译成动态链接库,把动态链接库加入到java项目当中,通过System.loadLibrary(String)方法加载,最后就可以调用了。
用CLion实现本地方法并给java调用的更多相关文章
- native关键字(本地方法)、 java调用so动态链接库
Java native关键字 一. 什么是Native Method 简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个ja ...
- java高级用法之:在JNA中将本地方法映射到JAVA代码中
目录 简介 Library Mapping Function Mapping Invocation Mapping 防止VM崩溃 性能考虑 总结 简介 不管是JNI还是JNA,最终调用的都是nativ ...
- 从本地方法栈看到jni调用
我们都知道java虚拟机所管理的内存区域包括方法区,堆,虚拟机栈,本地方法栈,程序计数器. 在<深入理解java虚拟机>中,周志明老师对虚拟机栈进行了讲解,但是对本地方法栈却一笔带过.今天 ...
- clob字段的值插入和查询N种方法【包括java调用存储过程传入clob参数】
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import jav ...
- Android Java访问本地方法(JNI)
当功能需要本地代码实现的时候,Java 代码就需要调用本地代码. 在调用本地代码时,首先要保证本地代码被加载到 Java 执行环境中并与 Java 代码连接在一起,这样 Java 代码在调用本地方法时 ...
- JVM 运行时数据区:程序计数器、Java 虚拟机栈和本地方法栈,方法区、堆以及直接内存
Java 虚拟机可以看作一台抽象的计算机,如同真实的计算机,它也有自己的指令集和运行时内存区域. Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存(运行时内存区域)划分为若干个不同的数 ...
- jvm入门及理解(三)——运行时数据区(程序计数器+本地方法栈)
一.内存与线程 内存: 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了JAVA在运行过程中内存申请.分配.管理的策略,保证了JVM的 ...
- JVM 专题九:运行时数据区(四)本地方法栈
1. 本地方法栈 2. 什么是本地方法栈? Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用 本地方法栈,也是线程私有的. 允许被实现成固定或者是可动态拓展的内存大小 ...
- JVM运行时数据区--本地方法栈
本地方法栈 1.Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法(一般非Java实现的方法)的调用 2.本地方法栈,也是线程私有的. 3.允许被实现成固定或者是可动态拓展的内存 ...
随机推荐
- 绕过网站WAF(图片绕过)
当我们在渗透一个网站的时候,很多时候,会遇到下面这种情况.网站装有WAF,把我们的SQL注入语句给拦截了. 这就是网站的安全狗 此时,我们的渗透会陷入僵局.到底应该如何才能让我们的语句绕过安全狗的检查 ...
- Win64 驱动内核编程-16.WFP网络监控驱动(防火墙)
WFP驱动监控网络 WFP 是微软推出来替代 TDI HOOK.NDIS HOOK 等拦截网络通信的方案,WFP 的框架非常庞大,在 RING3 和 RING0 各有一套类似的函数,令人兴奋的是,即使 ...
- OpenStack+kvm虚拟机xml格式解析
配置说明 首先介绍一下配置结构: xml配置遵循<keyword> xxxxxx </keyword>的格式,即一个配置段以<keyword>开头,以</ke ...
- python模块一之faker模块
faker模块主要是用来创建伪数据,无需手动生成或者手写随机数来生成数据,可以利用faker完成伪造大量测试数据的工作 一.安装 pip install faker 二.使用 from faker i ...
- Govern EventBus - 历经多年生产环境验证的事件驱动架构框架
Govern EventBus Govern EventBus 是一个历经四年生产环境验证的事件驱动架构框架, 通过事件总线机制来治理微服务间的远程过程调用. 使用本地事务来支持微服务内强一致性,事件 ...
- 一道VM的逆向所引发的符号执行思路
逆向虚拟机保护 虚拟机保护类的题目需要找到虚拟机的vm_code(字节码),各个handler,然后进一步分析虚拟机保护代码的流程. 用IDA打开程序,经分析后0x403040全局变量地址处存储的就是 ...
- 【BUAA软工】Beta阶段测试报告
项目 内容 班级:北航2020春软件工程 博客园班级博客 作业:Beta阶段测试报告 测试报告 发现的bug Beta阶段的bug [已解决]CPP项目无法进行调试 [已解决] 注册界面注册后不能够自 ...
- 自定义元类 __call__,__init__,__new__总结
只要对象能被调用 产生对象的类里必然有__call__方法 在调用类时,必定先触发type里的__call__ __call__下有: 1.产生对象的object.__new__ 2..被调用的类自己 ...
- Linux_控制服务与守护进程
一.systemd 1.systemd简介 1️⃣:systemd是用户空间的第一个应用程序,即/sbin/init 2️⃣:init程序的类型: SysV风格:init(centos5),实现系统初 ...
- Heap Size 与 Stack Size
Heap Size:如果没有用到标准库的malloc,就是废物,纯属浪费内存,所以直接设置为0即可: Stack Size :最大的可读写变量的大小即可: