IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java JNI(Java Native Interface)介绍 -> 正文阅读

[Java知识库]Java JNI(Java Native Interface)介绍

1. JNI 简介

众所周知,Java 的主要优势之一是它的可移植性,这意味着一旦我们编写并且编译了代码,这个过程的结果就是不依赖于平台的字节码。它可以像我们预期的那样运行在任何能够运行 Java 虚拟机的机器或设备上。

但是,有时我们确实需要使用一些为某些特定架构而进行本地编译的原生代码。例如:

  1. 需要对硬件执行某些操作
  2. 对性能要求非常苛刻
  3. 想要重用的现有库,而不是用 Java 重写它。

为了实现这一点,JDK 在我们的 JVM 中运行的字节码和原生代码(通常用 C 或 C++ 编写)之间搭建了一座桥梁。该桥梁就称为Java Native Interface。

2. JNI 如何工作

Java 提供了 native 关键字,用于指明该方法的实现将由原生代码提供。native 关键字将我们的方法转换为一种抽象方法:

private native void aNativeMethod();

在这里,这个方法的实现不是由另一个 Java 类实现,而是在一个分离的原生动态共享库中实现。它将在内存中构造一个表,其中包含指向我们所有原生方法实现的指针,以便可以从 Java 代码中调用它们。

让 JNI 工作起来所需要的一些关键组件如下:

  • Java 代码 - 我们的类,它将至少包含一种本地方法。
  • 原生代码 - 我们原生代码的实际逻辑,通常使用?C 或者 C++ 代码。
  • JNI 头文件 - 这个 C/C++ 的头文件 (jni.h),包括了我们可以在原生程序中使用的所有JNI 元素。
  • C/C++ 编译器 - 用于为我们的平台生成原生共享库。

代码中的 JNI 组件包括了 Java 和 C/C++ 代码。

Java 代码:

  • "native" 关键字 - 标记为 native 的方法都必须在原生共享库中实现。
  • System.loadLibrary(String libname) - 一种静态方法,用于将共享库从文件系统加载到内存中,并使其包含的函数可用于我们的 Java 代码。

C/C++ 代码:

  • JNIEXPORT - 将共享库中的函数标记为可导出,它将包含在函数表中,因此 JNI 可以找到它。
  • JNICALL - 与 JNIEXPORT 结合使用,确保我们的方法可用于 JNI 框架。
  • JNIEnv - 一个包含方法的结构,可以使用我们的原生代码访问 Java 元素。
  • JavaVM -?一种让我们可以操纵正在运行的 JVM(甚至启动一个新的 JVM)的结构,向它添加线程、销毁它等等。

3. 编写 hello world JNI

3.1 创建 Java 类

受限编写我们的 Java 代码?HelloWorldJNI.java,具体如下,此类中用 native 关键字定义了需要 C/C++ 实现的原生方法 sayHello()

public class HelloWorldJNI {
    static {
        System.loadLibrary("native");
    }
    
    public static void main(String[] args) {
        new HelloWorldJNI().sayHello();
    }

    // 定义原生sayHello()方法
    private native void sayHello();
}

3.2 通过 Java 类生成 C/C++ 所需的头文件

通过 Java 类自动生成 sayHello() 方法的定义,并保存在 HelloWorldJNI.h 头文件中

javac -h . HelloWorldJNI.java

自动生成的 HelloWorldJNI.h 的头文件内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorldJNI */

#ifndef _Included_HelloWorldJNI
#define _Included_HelloWorldJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorldJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorldJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

函数名是使用完全限定的包名、类名和方法名自动生成的。这个函数有两个参数,1 个是指向当前 JNIEnv 的指针,另一个是该方法附加到的 Java 对象,HelloWorldJNI 类的实例。

3.3 编写 C/C++?文件,完成 sayHello() 的函数实现。

需要为 sayHello 函数的实现创建一个新的 c/cpp 文件,文件里面包含了函数的具体实现。将此文件和 .h 使用相同的命名。

以 C 代码举例,HelloWorldJNI.c 的具体实现如下:

#include <stdio.h>
#include "HelloWorldJNI.h"

JNIEXPORT void JNICALL Java_HelloWorldJNI_sayHello
  (JNIEnv* env, jobject thisObject) {
    printf("hello world!\n");
}

3.4 编译 C 代码,生成共享库

我们已经实现了所有代码的编写,接下来需要从 C 代码编译成共享库,将共享库命名为 libnative.so 。

[root@wuhan hello]# export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/                     
[root@wuhan hello]# gcc -shared -fPIC -o libnative.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloWorldJNI.c -lc

目前为止,我们当前的文件夹底下应该包含了如下文件:

[root@wuhan hello]# ls
HelloWorldJNI.c  HelloWorldJNI.class  HelloWorldJNI.h  HelloWorldJNI.java  libnative.so

3.5 运行 Java 程序

最后,运行我们的 Java 程序,即可获得原生代码的输出 "hello world!"

[root@wuhan hello]# java -cp . -Djava.library.path=. HelloWorldJNI                                      
hello world!

4. 定义带参数和返回值的函数方法

只调用原生的 C 代码并打印 "hello world!" 肯定是不能满足编码要求的。函数需要有非空的入参和返回值才能处理更多的事情。

在 HelloWorldJNI 类里面新增一个?sumIntegers 的方法,有两个 int 入参和一个 long 返回值。

public class HelloWorldJNI {
    static {
        System.loadLibrary("native");
    }
    
    public static void main(String[] args) {
        new HelloWorldJNI().sayHello();
        long sum = new HelloWorldJNI().sumIntegers(10, 20);
        System.out.println("sum:" + sum);
    }

    // 定义原生sayHello()方法
    private native void sayHello();
    // 定义原生sumIntegers方法,返回值是long,有两个int入参
    private native long sumIntegers(int first, int second);
}

查看生成的头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorldJNI */

#ifndef _Included_HelloWorldJNI
#define _Included_HelloWorldJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorldJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorldJNI_sayHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloWorldJNI
 * Method:    sumIntegers
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_HelloWorldJNI_sumIntegers
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

同样的编译运行步骤,可以得到 sumIntegers 的运行结果。

[root@wuhan hello]# java -cp . -Djava.library.path=. HelloWorldJNI
hello world!
received first:10 second:20
sum:30

可以在?Oracle 官方文档中查看 Java 类型和等价的?C JNI 类型。Java Native Interface Specification: 3 - JNI Types and Data Structures

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-16 22:07:36  更:2022-03-16 22:08:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:24:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码