[JNI]使用jni实现简单的Java调用本地C语言代码

[JNI]使用jni实现简单的Java调用本地C语言代码

JNI的解释

Java Native Interface,即Java本地接口。

在Java官方描述中为:

The JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly.

JNI是一个本地编程接口。它允许运行在Java虚拟机(JVM)内部的Java代码与其他编程语言(如C、C++和汇编)编写的应用程序和库进行互操作。

The most important benefit of the JNI is that it imposes no restrictions on the implementation of the underlying Java VM. Therefore, Java VM vendors can add support for the JNI without affecting other parts of the VM. Programmers can write one version of a native application or library and expect it to work with all Java VMs supporting the JNI.

JNI最重要的好处是它对底层Java虚拟机的实现不施加任何限制。因此,Java虚拟机的供应商可以添加对JNI的支持而不影响VM的其他部分。程序员可以编写一个版本的本地应用程序或库,并期望它能与所有支持JNI的Java虚拟机兼容。

简而言之,jvm提供了Java和C/C++/汇编代码的桥梁,可以对Java进行扩展,这个桥梁就是JNI

介绍表情包

我们知道,Java的主要优势之一是其可移植性——这意味着一旦我们编写并编译代码,这个过程的结果就是平台独立的字节码。

简单来说,这可以在任何能够运行Java虚拟机的机器或设备上运行,而且它的工作会像我们所期望的那样无缝进行。

然而,有时我们确实需要使用为特定架构原生编译的代码。

需要使用原生代码可能有一些原因:

  • 需要处理某些硬件
  • 对非常苛刻的过程进行性能提升
  • 我们希望复用而不是用Java重写的现有库。

为了实现这一点,JDK引入了一个桥梁,连接了在我们的JVM中运行的字节码和原生代码(通常用C或C++编写)。

这玩意儿咋工作的表情包

本地方法Native Methods:Java虚拟机与编译代码的交互

java 提供了 native关键字,用于指示该方法的实现将由本地代码提供,因此你是没办法在Java代码里看到这个方法的具体实现的。

比如我在研究ArrayList如何addAll数据时:

在这里插入图片描述

发现其中执行了一段代码:System.arraycopy(a, 0, elementData, size, numNew);

这段代码很显然是为了实现数组的复制,因此我想看看到底是怎么copy的,点进该方法一看:

在这里插入图片描述

好好好,native修饰的,Java代码里看不了,这说明System.arraycopy是用的本地C/C++代码实现的

通常来说,在C语言或C++语言中创建本地可执行程序时,我们可以选择编译为静态链接库动态链接库或者可执行文件,可执行文件是经历了链接之后的最终产物,不说了,我们:

静态链接库

所有库的二进制文件将在链接过程中作为我们可执行文件的一部分被包含进去。因此,我们不再需要这些库,但这会增加我们可执行文件的大小。在编译时链接,程序包含库的副本,运行时不需要库文件,但可能导致文件较大和内存使用效率低。

  1. 链接时机:静态链接库在编译时被链接到程序中。当编译器编译一个使用静态库的程序时,它会将程序中实际用到的库中的代码和数据复制到最终的可执行文件中。
  2. 文件大小:由于静态库的代码和数据被复制到每个使用它的程序中,这可能导致最终的可执行文件较大,尤其是当多个程序使用相同的静态库时。
  3. 运行时依赖:静态链接的程序在运行时不需要库文件,因为所有需要的代码和数据都已经包含在可执行文件中。
  4. 更新和维护:如果静态库更新了,所有使用它的程序都需要重新编译以包含新版本的库。
  5. 内存使用:由于每个程序都有自己的库副本,这可能导致内存使用效率较低,因为相同的库代码可能在多个程序中重复加载。

动态链接库

最终的可执行文件只包含对库的引用,而不是代码本身。它要求我们运行可执行文件的环境能够访问我们程序使用的所有库文件。 在运行时链接,程序不包含库的副本,运行时需要库文件,但可以减小文件大小和提高内存使用效率。

  1. 链接时机:动态链接库在程序运行时被链接。编译器在编译时只记录程序使用了哪些动态库,而不将库的代码和数据复制到可执行文件中。
  2. 文件大小:动态链接的程序通常比静态链接的程序小,因为它们不包含库的代码和数据。
  3. 运行时依赖:动态链接的程序在运行时需要库文件。如果库文件不存在或版本不匹配,程序可能无法运行。
  4. 更新和维护:动态库的更新不需要重新编译使用它的程序。只需替换库文件,所有使用该库的程序都可以使用新版本的库。
  5. 内存使用:动态库在内存中只有一份副本,多个程序可以共享,这提高了内存使用效率。

后面这种动态链接的做法对JNI来说才靠谱,因为我们不能把字节码和本地编译的代码混在一个二进制文件里。

所以,我们的动态链接库会把本地代码单独编译在.so、.dll或者.dylib文件里,而不是混在类文件里。

文件类型文件描述对应系统
.so共享对象(Shared Object)Unix 和 类Unix操作系统(如 Linux)
.dll动态链接库(Dynamic Link Library)Windows 操作系统
.dylib动态库(Dynamic Library)较早的macOS 操作系统

正如前面所看到的那样,native关键字修饰的方法我们称之为本地方法,由非Java代码实现

public native void theNativeMethod();

这个代码和 public void theMethod();的主要不同点在于:

这个方法不是由另一个Java类来实现的,而是由一个独立的本地共享库来实现,为了让我们能够从Java代码中调用这些本地方法,Java虚拟机会创建一个包含指向这些方法实现内存地址的指针表

重要组成部分表情包

下面简单说说几个重要的部分,我们得注意一下。

  • Java代码 – 就是我们的Java类。这些类里至少会有一个标记为“本地”的方法。就像是我们写的Java程序,里面会有一些特殊的方法,我们称它们为“本地方法”,这些方法不是用Java写的,而是用其他语言写的。

  • 本地代码 – 这是我们本地方法的实际执行逻辑,通常是用C语言或C++语言编写的。 这些是用C语言或C++语言写的代码,它们是那些“本地方法”的实现部分。

  • JNI头文件 – 这是一个给C/C++用的头文件(在JDK目录下的include/jni.h),里面包含了我们在本地程序中可能用到的所有JNI元素的定义。 这是一个特殊的文件,里面有很多关于如何让Java代码和本地代码相互交流的规则。

  • C/C++编译器 – 我们可以选择GCC、Clang、Visual Studio,或者任何我们喜欢的编译器,只要它能帮我们生成一个适用于我们平台的本地共享库就行。

JNI代码里的重要元素(JAVA和C/C++)

Java元素

  • “native”关键词:我们之前说过,任何标记为native的方法都必须在一个本地共享库中实现。
  • System.loadLibrary(String libname):一个静态方法,它可以从文件系统中加载一个共享库到内存,并让其导出的函数能够被我们的Java代码使用。

C/C++元素(很多都在jni.h中定义):

  • JNIEXPORT:用来标记共享库中的函数为可导出的,这样它就会被包含在函数表中,JNI就能找到它了。
  • JNICALL:和JNIEXPORT一起使用,确保我们的方法可以被JNI框架使用。
  • JNIEnv:一个结构体,包含了一些方法,让我们的本地代码能够访问Java元素。
  • JavaVM:一个结构体,允许我们操纵正在运行的JVM(甚至可以启动一个新的),增加线程,销毁等…

初次使用JNI表情包

接下来,我们要学习一下如何使用jni

根据前面的内容我们知道,肯定是需要有个编译器,编译C/C++的

我这里选择的windows平台的 MinGW进行编译(MinGW安装自行百度)

搭建好编译环境后:

新建一个Java类文件

public class TestJNI{
	
	// 静态代码块
	// 为了在运行时加载本地动态链接库,windows是 TestJNI.dll,linux是 TestJNI.so
	// 这个动态链接库中包含有sayHello方法的实现
	static{
		System.loadLibrary("TestJNI");
	}
	
	// 在Java类中声明一个实例本地方法sayHello()
	private native void sayHello();
	
	// main方法,测试,用来创建类的实例并调用本地方法
	public static void main(String[] args){
		new TestJNI().sayHello();
	}
}

代码的解释:

静态初始化器会在类加载的时候调用System.loadLibrary()来加载名为"TestJNI"的本地库(这个库包含一个叫做sayHello()的本地方法)。在Windows系统中,它对应的是"hello.dll";在Unix或Mac OS X系统中,对应的是"libTestJNI.so"。这个库必须被包括在Java的库路径里(这个路径保存在Java系统变量java.library.path中)。你可以通过虚拟机参数-Djava.library.path=链接库路径来把库加入Java的库路径。如果运行时找不到这个库,程序会抛出一个UnsatisfiedLinkError。(这个系统变量java.library.path会在后续代码中进行描述)

接下来,我们通过关键词native声明sayHello()方法为一个本地实例方法,这意味着这个方法是用另一种语言实现的。一个本地方法是没有方法体的。sayHello()方法应该在我们已经加载的本地库中被找到。

main()方法会创建一个TestJNI的实例,并调用本地方法sayHello()。

编译Java文件并且生成对应的C/C++头文件

进入命令行到 TestJNI.java所在文件目录下

通过命令生成对应的JNI规范.h头文件

// java8及以后
javac -h . TestJNI.java

// Java8以前
javac TestJNI.java
javah TestJNI

生成的文件如图:

在这里插入图片描述

我们现在点进TestJNI.h中看看里面是个啥?

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>  // 这个头文件中包含了JNI所有必要的类型定义和函数声明
/* Header for class TestJNI */

// 如果宏_Included_TestJNI没有在其他地方定义过,就将下面的内容进行定义。
//(这个宏包含的范围一直到最后一个#endif)
#ifndef _Included_TestJNI
#define _Included_TestJNI


// 告诉编译器下面的代码应该要以C语言的链接方式进行处理
// 这是为了解决c++的函数重载导致符号名称变化的问题,从而使得Java可以正确的调用本地方法
#ifdef __cplusplus
extern "C" {
#endif

// 本地方法的函数声明
/*
 * Class:     TestJNI    类名为 TestJNI
 * Method:    sayHello   方法名为 sayHello
 * Signature: ()V        方法签名是()V 表示无参且返回值为void的方法
 */
JNIEXPORT void JNICALL Java_TestJNI_sayHello
  (JNIEnv *, jobject);

    
#ifdef __cplusplus
}
#endif
// 这里结束了extern "C"块,即告诉C++编译器,后续的代码将继续使用C++的链接方式。
#endif
// 结束 _Included_TestJNI 的宏定义

上述代码解释:

头文件里声明了一个C语言函数,名为Java_TestJNI_sayHello,来源于固定的命名规则,规则为Java_{包名}_{类名}_{方法名}(JNI参数),其中包名的点会被下划线代替

参数中包含有:

  • JNIEnv*:指向JNI环境的引用,让你能够访问所有的JNI函数。
  • jobject:指向"this" Java对象的引用。

我们暂时先忽略掉 JNIEXPORTJNICALL 宏 定义,在零汇编的平台中,这些定义均为空,在x86或者sparc平台中会根据不同的定义可能会有实现。

#ifdef __cplusplus时会被c++编译器识别,也就是说extern "C"只会被c++编译器识别出来,它告诉c++编译器下面的函数需要按照C语言的函数命名规则来进行编译,不是以C++的函数命名规则

C++编译器对于函数名有特定的处理方式,即名称修饰或者称为名称变形,或者说不以C++的函数签名的方式进行编译

这是一种在编译阶段发生的处理,用于支持C++的特性,比如函数重载。由于C++允许多个函数共享相同的名字,只要它们的参数类型不同,编译器需要一种方法来区分这些函数的符号名,因此会对函数名进行修饰,加入额外的信息,如函数的参数类型和数量。

怎么理解呢?

这就像是你有一本做饼干的食谱,但是食谱上的指令是给两种不同厨房设备(C和C++)使用的。现在,你想用C++设备来做饼干,但是这个设备有自己的一套复杂的指令(比如可以做多种口味的饼干),所以你需要告诉它:“虽然你很牛逼,但得按照普通的C设备(只做一种饼干)的方式来做这个饼干。” 这样它就不会搞错了。

#include <jni.h>这个存在于何处呢?

以Windows Java8 为例,这个头文件在你的JAVA_HOME/include里面可以找得到或者在其于当前系统相关的子目录中

比如我的Java环境是:C:\Program Files\Java\jdk1.8.0_261,在其子目录include里面就能看到

在这里插入图片描述

对于Linux中,我的JAVA_HOME是/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.302.b08-0.el8_4.x86_64 在这个文件夹下有include文件目录

在这里插入图片描述

编写C实现代码

在同一个目录下,创建一个文件名和刚刚生成的头文件名称一样的文件 “TestJNI.c”

在这里插入图片描述

下面编写文件中的代码

#include<stdio.h>    // C语言的基本输入输出
#include<jni.h>      // jni的头文件,这个是JDK里面包含的
#include "TestJNI.h" // 前面通过命令生成的头文件

// java sayHello方法的本地实现
JNIEXPORT void JNICALL Java_TestJNI_sayHello(JNIEnv *env, jobject obj)
{
    printf("Hello,This is my First JNI Code!!");
    return;
}

编译C程序

我这里是windows,就以windows为例

JAVA_HOME为C:\Program Files\Java\jdk1.8.0_261

使用指令

gcc -I "C:\Program Files\Java\jdk1.8.0_261\include" -I "C:\Program Files\Java\jdk1.8.0_261\include\win32" -shared -o TestJNI.dll TestJNI.c

注意你的JAVA_HOME和我的可能不同,需要自行修改

编译完成后,当前文件夹下会出现动态链接库文件 TestJNI.dll

在这里插入图片描述

运行Java文件

我直接执行命令:java TestJNI一定会报错
在这里插入图片描述

所以需要将编译后的动态链接库传递给虚拟机执行

使用命令

java -Djava.library.path=. TestJNI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/617201.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

day11-StreamFile

1.Stream流 1.1 体验Stream流 需求&#xff1a;按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素 把集合中所有以"杨"开头的元素存储到一个新的集合 把"杨"开头的集合中的长度为3的元素存储到一个新的集合 遍历上一步得到…

C++语言题库(三)—— PAT

目录 1. 打印点、圆、圆柱信息 2. 国际贸易统计 3. 设计一个类CRectangle 4. 定义一个时间类 5. 定义一个Date类 6. 定义一个Time类 7. 设计一个People类 8. 平均成绩 9. 计算若干个学生的总成绩及平均成绩 11. 使用面向对象的方法求长方形的周长 1. 打印点、圆、圆柱…

回溯算法精讲

原理 回溯&#xff0c;就和深度优先遍历&#xff08;DFS&#xff09;类似&#xff0c;属于先一层到底直至到终点&#xff0c;如果这条路径不对&#xff0c;则回退一下&#xff0c;再继续往下搜索。 抽象地说&#xff0c;解决一个回溯问题&#xff0c;实际上就是遍历一棵决策树…

【神经网络】输出层的设计

文章目录 前言一、恒等函数和softmax函数恒等函数softmax 函数python实现softmax函数 二、实现softmax函数时的注意事项函数优化python实现 三、softmax函数的特征计算神经网络的输出输出层的softmax函数可以省略“学习”和“推理”阶段 四、输出层的神经元数量 前言 神经网络…

Disk Map for Mac,让您的Mac更“轻”松

还在为Mac磁盘空间不足而烦恼吗&#xff1f;Disk Map for Mac来帮您轻松解决&#xff01;通过独特的TreeMap视觉显示技术&#xff0c;让您一眼就能看出哪些文件和文件夹占用了大量空间。只需简单几步操作&#xff0c;即可快速释放磁盘空间&#xff0c;让您的Mac更“轻”松。快来…

el-checkbox选中后的值为id,组件显示为label中文

直接上代码 方法一 <el-checkbox v-for"item in list" :key"item.id" :label"item.id">{{中文}} </el-checkbox> 方法二 <el-checkbox-group class"flex_check" v-model"rkStatusList" v-for"item…

prometheus、mysqld_exporter、node_export、Grafana安装配置

工具简介 Prometheus&#xff08;普罗米修斯&#xff09;&#xff1a;是一个开源的服务监控系统和时间序列数据库 mysqld_exporter&#xff1a; 用于监控 mysql 服务器的开源工具&#xff0c;它是由 Prometheus 社区维护的一个官方 Exporter。该工具通过连接到mysql 服务器并执…

EasyNmon服务器性能监控工具环境搭建

一、安装jdk环境 1、看我这篇博客 https://blog.csdn.net/weixin_54542209/article/details/138704468 二、下载最新easyNmon包 1、下载地址 https://github.com/mzky/easyNmon/releases wget https://github.com/mzky/easyNmon/releases/download/v1.9/easyNmon_AMD64.tar.…

openssl 生成证书步骤

本地测试RSA非对称加密功能时&#xff0c;需要用到签名证书。本文记录作者使用openssl本地生成证书的步骤&#xff0c;并没有深入研究openssl&#xff0c;难免会有错误&#xff0c;欢迎指出&#xff01;&#xff01;&#xff01; 生成证书标准流程&#xff1a; 1、生成私钥&am…

单位学校FM调频电台直放站系统

随着教育技术的不断发展&#xff0c;校园广播系统的建设已成为现代学校必不可少的一部分。作为传统有线广播的有效补充&#xff0c;基于无线电信号传输的 FM 调频电台在学校的使用日益广泛&#xff0c;尤其是在紧急通知、日常信息传播及教学辅助等方面发挥着重要作用。为了增强…

msvcp140dll怎么修复,分享5种有效的解决方法

MSVCP140.dll文件丢失这一现象究竟是何缘由&#xff0c;又会引发哪些令人头疼的问题呢&#xff1f;在探索这个问题的答案之前&#xff0c;我们先来深入了解这个神秘的DLL文件。MSVCP140.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它扮演着至关重要的…

IP地址定位技术在网络安全中的作用

在当今数字化时代&#xff0c;网络安全已经成为企业、政府和个人面临的重要挑战之一。随着互联网的普及和网络攻击的增加&#xff0c;保护个人隐私和防止网络犯罪变得尤为重要。在这一背景下&#xff0c;IP地址定位技术作为网络安全的重要组成部分之一&#xff0c;发挥着关键作…

【Shell】shell编程之循环语句

目录 1.for循环 例题 2.while循环 例题 3.until循环 1.for循环 读取不同的变量值&#xff0c;用来逐个执行同一组命令 for 变量 in 取值列表 do 命令序列 done [rootlocalhost ~]# for i in 1 2 3 > do > echo "第 $i 次跳舞" > done 第 1 次跳舞 第 …

Redis经典问题:数据不一致

大家好,我是小米,今天我想和大家聊一聊Redis的一个经典问题——数据不一致。在使用Redis的过程中,你是否曾遇到过这样的问题?缓存和数据库中的数据不一致,可能导致应用程序的功能异常。下面,我将详细介绍数据不一致的原因,以及一些有效的解决方案。 什么是数据不一致 …

WordPress插件Plus WebP,可将jpg、png、bmp、gif图片转为WebP

现在很多浏览器和CDN都支持WebP格式的图片了&#xff0c;不过我们以前的WordPress网站使用的图片都是jpg、png、bmp、gif&#xff0c;那么应该如何将它们转换为WebP格式的图片呢&#xff1f;推荐安装这款Plus WebP插件&#xff0c;可以将上传到媒体库的图片转为WebP格式图片&am…

picoCTF-Web Exploitation-Trickster

Description I found a web app that can help process images: PNG images only! 这应该是个上传漏洞了&#xff0c;十几年没用过了&#xff0c;不知道思路是不是一样的&#xff0c;以前的思路是通过上传漏洞想办法上传一个木马&#xff0c;拿到webshell&#xff0c;今天试试看…

多线程-线程安全

目录 线程安全问题 加锁(synchronized) synchronized 使用方法 synchronized的其他使用方法 synchronized 重要特性(可重入的) 死锁的问题 对 2> 提出问题 对 3> 提出问题 解决死锁 对 2> 进行解答 对4> 进行解答 volatile 关键字 wait 和 notify (重要…

如何在沉浸式翻译浏览器插件中使用免费的DEEPLX和配置API接口

如何在浏览器插件沉浸式翻译中使用DEEPLX 如何配置免费的DEEPLX翻译功能如何打开PDF翻译功能如何解除翻译额度限制 如何配置免费的DEEPLX翻译功能 假设你已经在浏览器上安装了沉浸式翻译插件&#xff0c;但是不知道如何使用免费的DEEPLX功能 这里以EDGE浏览器为例&#xff0c;…

JVM从1%到99%【精选】-类加载子系统

目录 1.类的生命周期 1.加载 2.连接 3.初始化 2.类的加载器 1.类加载器的分类 2.双亲委派机制 3.面试题&#xff1a;类的双亲委派机制是什么&#xff1f; 4.打破双亲委派机制 1.类的生命周期 类加载过程&#xff1a;加载、链接&#xff08;验证、准备、解析&a…

# 从浅入深 学习 SpringCloud 微服务架构(十七)--Spring Cloud config(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;十七&#xff09;–Spring Cloud config&#xff08;1&#xff09; 一、配置中心的 概述 1、配置中心概述 对于传统的单体应用而言&#xff0c;常使用配置文件来管理所有配置&#xff0c;比如 SpringBoot 的 application.y…