首页 > 编程语言 >Java JNI 学习笔记

Java JNI 学习笔记

时间:2024-07-15 23:31:23浏览次数:19  
标签:Java java 笔记 C++ HelloJNI JNI include

Java JNI 学习笔记

JNI(Java Native Interface)是 Java 提供的一种接口,使得 java 代码可以与其他语言(如 C 和 C++)编写的代码进行交互。具体来说,JNI 允许你在 Java 中调用本地(Native)代码,或者从本地代码调用 Java 方法。

基本概念

  • jni.h:这是 JNI 的头文件,使用 javac 生成,定义了 JNI 的接口函数和数据结构
  • jni.cpp:这是实现了本地方法的 C++ 文件,包含了具体的本地代码
  • jni.so:这是生成的共享库文件(在 windows 上为 .dll 文件),java 程序通过它调用本地方法

Demo:java 调用 C++ 代码

编写 java 类

创建一个 Java 类并声明一个本地方法。

// HelloJNI.java
public class HelloJNI {
    // 加载本地库
    static {
        System.loadLibrary("hello"); // Load native library at runtime
                                     // hello.dll (Windows) or libhello.so (Unixes)
    }
    
    // 声明一个本地方法
    private native void sayHello();

    // 主方法
    public static void main(String[] args) {
        new HelloJNI().sayHello(); // 调用本地方法
    }
}

上面代码的静态代码块在这个类被类加载器加载的时候调用了 System.loadLibrary 库来加载一个 native 库 “hello”,这个库实现了 sayHello 函数。接下来,我们使用 native 关键字将 sayHello() 方法声明为本地实例方法。注意,一个 native 方法不包含方法体,只有声明。上面代码中的 main 方法实例化了一个 HelloJJNI 类的实例,然后调用了本地方法 sayHello()

生成 C++ 头文件

编译 Java 类并使用 javac 生成 JNI 所需要的 C++ 头文件。

# 编译 Java 类并生成 C++ 头文件 
javac -h . HelloJNI.java

此时会生成一个名为 HelloJNI.h 的头文件:

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

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

# ifdef __cplusplus
}
# endif
# endif

上面的头文件生成了一个 Java_HelloJNI_sayHello 的 C 函数:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

将 java 的 native 方法转换成 C 函数声明的规则是这样的:Java_{package_and_classname}_{function_name}(JNI arguments)。包名中的点换成单下划线。需要说明的是生成函数中的两个参数:

  1. JNIEnv *:这是一个指向 JNI 运行环境的指针,我们可以通过这个指针访问 JNI 函数
  2. jobject:指代 java 中的 this 对象

头文件中有一个 extern “C”,同时上面还有 C++ 的条件编译语句,这里的函数声明是要告诉 C++ 编译器:这个函数是 C 函数,请使用 C 函数的签名协议规则去编译!因为我们知道 C++ 的函数签名协议规则和 C 的是不一样的,因为 C++ 支持重写和重载等面向对象的函数语法。

编写 C++ 实现

创建一个 C++ 文件,实现头文件中声明的本地方法。

// HelloJNI.cpp
# include <jni.h>
# include <iostream>
# include "HelloJNI.h"

// 实现本地方法:注意函数名称需要和头文件中的函数名称保持一致
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
    std::cout << "Hello from C++!" << std::endl;
}

编译 C++ 代码生成共享库

将 C++ 文件编译为共享库文件:

# Linux/macOS
g++ -shared -fpic -o libhello.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloJNI.cpp
g++ -shared -fpic -o libllm_jni.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux llm_jni.cpp
# Windows
g++ -shared -o hello.dll -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.cpp

运行 Java 程序

确保共享库文件位于 Java 的库路径中,然后运行 java 程序:

# 运行 Java 程序
java -Djava.library.path=. -cp . HelloJNI

# -Djava.library.path=. : 指定 java 查找本地库的路径
# -cp . :设置 java 的类路径(class path),即查找 java 类文件的路径

执行上述命令后,你应该会看到输出:

Hello from C++!

在 java 和 Native 代码之间传递参数和返回值

传递基本类型

传递 java 的基本类型是非常简单而直接的,一个 jxxx 之类的类型已经定义在本地系统中了,比如:jint,jbyte,jshort,jlong,jfloat,jdouble,jchar 和 jboolean 分别对应 java 的 int,byte,short,long,float,double,char 和 boolean 基本类型。

Java JNI 程序:

public class TestJNIPrimitive {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);

   // Test Driver
   public static void main(String args[]) {
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}

生成头文件:javac -h . TestJNIPrimitive.java

头文件 TestJNIPrimitive.h 中包含了一个函数声明:

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint);

可以看到,这里的 jint 和 jdouble 分别表示 java 中的 int 和 double。

TestJNIPrimitive.cpp 的实现如下:

// TestJNIPrimitive.cpp
# include <jni.h>
# include <iostream>
# include "HelloJNI.h"

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *env, jobject obj, jint n1, jint n2) {
    std::cout << "In C++, the numbers are " << n1 << " and " << n2  << std::endl;
    jdouble result;
    result = ((jdouble)n1 + n2) / 2.0;
    return result;
}

编译为共享库并运行:

g++ -shared -fpic -o libmyjni.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux TestJNIPrimitive.cpp
java -Djava.library.path=. -cp . TestJNIPrimitive

传递字符串

Java JNI 程序:

public class HelloJNI {
    static {
        System.loadLibrary("hello");
    }

    public native String sayHello(String msg);
    
    public static void main(String[] args) {
        String res = new HelloJNI().sayHello("Hello, JNI");
        System.out.println("JNI Results: " + res);
    }
}

生成头文件:javac -h . HelloJNI.java

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject, jstring);

编写 C++ 实现:

// HelloJNI.cpp
# include <jni.h>
# include <iostream>
# include "HelloJNI.h"

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj, jstring inJNIStr) {
    if (inJNIStr == NULL) {
        return NULL;
    }
    
    // Convert the JNI String (jstring) into C-String (char*)
    const char* inCStr = env->GetStringUTFChars(inJNIStr, NULL);
    if (inCStr == NULL) {
        return NULL; 
    }
    // Log the received string
    std::cout << "In C++, the received string is: " << inCStr << std::endl;
    // Perform operations on the string
    std::string resultStr = std::string(inCStr) + " from C++";
    // Release the JNI String resources
    env->ReleaseStringUTFChars(inJNIStr, inCStr);
    // Convert the modified C-string back into JNI String (jstring) and return
    return env->NewStringUTF(resultStr.c_str());
}

注意,传递一个字符串比传递基本类型要复杂得多,因为 java 的 String 是一个对象,而 C 的 string 是一个 NULL 结尾的 char 数组。因此,我们需要将 java 的 String 对象转换成 C 的字符串表示形式:char *。

前面我们提到,JNI 环境指针 JNIEnv * 已经为我们定义了非常丰富的接口函数来处理数据的转换:

  1. 调用 const char* GetStringUTFChars(jstring, jboolean*) 来将 JNI 的 jstring 转换成 C 的 char *
  2. 调用 jstring NewStringUTF(char*) 来将 C 的 char * 转换成 JNI 的 jstring

编译生成共享库并运行:

g++ -shared -fpic -o libhello.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloJNI.cpp
java -Djava.library.path=. -cp . HelloJNI

参考资料

  1. Java Native Interface (JNI) 从零开始详细教程
  2. 一篇文章教你完全掌握 jni 技术

标签:Java,java,笔记,C++,HelloJNI,JNI,include
From: https://www.cnblogs.com/lockegogo/p/18304234

相关文章

  • UDP网络编程java实现
    UdpUdp服务端实现步骤:创建Udp对象,监听端口创建数据包(数据包,数据长度)接收数据包(数据包)读取数据包,并输出将字节数组转化为字符串响应客户端消息(设置数据包)发送数据包//监听端口号DatagramSocketdatagramSocket=newDatagramSocket(6666);//创建数据包byte[]buff......
  • 【Android面试八股文】1. 说一说Java四大引用有哪些? 2. 软引用和弱引用的区别是什么?
    一、Java四大引用有哪些?在Java中,有四种不同类型的引用,它们在垃圾回收和对象生命周期管理方面有着不同的作用和行为。这四种引用分别是:强引用(StrongReference)软引用(SoftReference)弱引用(WeakReference)虚引用(PhantomReference)下面详细解释每种引用的特点和用途:......
  • MongoDB自学笔记(二)
    一、前言接着上一篇文章,在上一篇文章中学习了如何使用数据库、如何创建集合、如何往集合里添加文档,今天我们继续学习一下更新文档,更新文档相对来说比较复杂笔者打算分多次来记录学习过程。二、文档操作1、更新文档基础语法:db.collection.updateOne(filter,update,opt......
  • [Java基础]HashMap
    HashSet基于哈希表实现的无序集合,它使用哈希算法来存储和检索元素。下面是向HashSet中加入元素的过程:计算哈希码(HashCode):当你向HashSet中添加一个元素时,首先会调用该元素的hashCode()方法,得到元素的哈希码。如果元素为null,则它的哈希码为0。映射到桶位置(BucketP......
  • JavaScript全解析——本地存储✔(localStorage~sessionStorage~cookie)
    ●就是浏览器给我们提供的可以让我们在浏览器上保存一些数据●常用的本地存储(localStorage~sessionStorage~cookie)1-localStorage=>特点:->长期存储,除非手动删除否则会一直保存在浏览器中清除缓存或者卸载浏览器也就没有了->可以跨页面通讯,也就是说在一个页面写下......
  • Java 中有哪几种基本数据类型?请分别列出它们并简述每种数据类型的特点及其在内存中的
    在Java的世界里,数据是构建应用程序的基石。为了高效地处理这些数据,Java设计了一系列基础数据类型,它们直接映射到计算机硬件上,因此在性能和内存使用上更为高效。我们常说的Java八大基本数据类型,涵盖了整数、浮点数、字符和布尔值,下面我将一一介绍它们的特点以及在内存中的占用......
  • Java 中如何实现接口和抽象类,它们的主要区别是什么?
    在Java编程中,接口(Interface)和抽象类(AbstractClass)是实现抽象化的两种重要手段,它们帮助我们设计更灵活、可扩展的代码结构。下面,我将从定义、实现方式、主要区别以及应用场景等方面,用平实的语言和示例代码来阐述它们的使用。接口(Interface)定义:接口是一种完全抽象的类型,它只......
  • LeetCode算法笔记5
    题目描述给你一个字符串 s,找到 s 中最长的 回文子串示例1:输入:s="babad"输出:"bab"解释:"aba"同样是符合题意的答案。示例2:输入:s="cbbd"输出:"bb"提示:1<=s.length<=1000s 仅由数字和英文字母组成解法:classSolution:deflongestPalindrome(sel......
  • LeetCode算法笔记2
    题目描述给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字0之外,这两个数都不会以0 开头。示例1:输入:l1=[2,4,3],l......
  • 什么是 Java 中的静态变量和静态方法?它们在类与对象间的关系是如何体现的?
    在Java编程的世界里,静态变量和静态方法是两个非常基础且重要的概念,它们让我们的代码更加灵活和高效。想象一下,你正在设计一个班级管理系统,每个学生都有姓名和学号,但班级的名称只有一个,对所有人共享。这里的班级名称就可以用静态变量来表示,因为它不属于任何一个特定的学生,而是......