找回密码
 立即注册
首页 业界区 安全 C/C++与Java混合的JNI编程

C/C++与Java混合的JNI编程

茅香馨 2025-8-8 05:53:36
Java与C++混合编程可以实现两种语言的优势结合,C++的程序性能很高且支持强大的系统调用能力,Java则生态丰富且开发效率较高。JNI是Java与C++进行混合编程的关键桥梁,本章将基于JNI技术讲述Java与C++混合编程的方法和技巧。
1. Java与JNI

1.1. 什么是Java?

Java是一种高级编程语言,也是一个计算平台(通常指Java虚拟机)。最初由Sun Microsystems公司(后被Oracle收购)的James Gosling和他的团队在1995年发布。Java语言的设计目标是简单性、健壮性和跨平台兼容性。以下是Java的一些关键特点:

  • 面向对象: Java是一种面向对象的语言,这意味着它基于对象和类的概念。对象代表现实世界中的实体或概念,而类是创建对象的模板。
  • 平台无关性: Java的一个核心特性是“一次编写,到处运行”(Write Once, Run Anywhere,WORA)。Java程序在执行前会被编译成字节码,这种中间形式的代码可以在任何安装了Java虚拟机(JVM)的设备上运行。
  • 自动内存管理: Java提供了自动垃圾回收机制,这意味着程序员不需要手动管理内存的分配和释放,从而减少了内存泄漏和其他内存相关错误。
  • 丰富的标准库: Java拥有一个庞大的标准库(也称为Java API),提供了大量预先构建的类和接口,用于处理文件输入/输出、网络编程、多线程编程、数据结构等。
  • 跨平台兼容性: Java不仅可以在不同的操作系统上运行,还可以在嵌入式系统、移动设备和大型服务器上运行。
Java的应用场景广泛,是目前最流行的后端系统开发语言,此外Java还是Android系统的主要编程语言,绝大部分的Android应用程序都基于Java语言进行开发。
1.2. 什么是JVM?

JVM(Java Virtual Machine)是一个可以执行Java字节码的虚拟计算机。它是Java平台的核心组成部分,提供了Java程序运行所需的环境。JVM是Java语言能做到“一次编写,到处运行”的基础。以下是JVM的一些关键特点和功能:

  • 平台无关性:JVM的主要目的是实现Java的跨平台特性。Java源代码被编译成平台无关的字节码,这些字节码可以在任何安装了相应JVM的设备上运行。
  • 字节码解释器:JVM包含一个字节码解释器,它负责将字节码转换为特定平台的机器码。这个过程使得Java程序可以在不同的操作系统和硬件上运行。
  • 即时编译器(JIT):为了提高性能,现代JVM通常包含一个即时编译器。JIT编译器会将热点代码(频繁执行的代码)编译成优化的机器码,以提高执行效率。
  • 垃圾回收:JVM负责管理内存分配和回收。它提供了自动垃圾回收机制,帮助程序员管理内存,减少内存泄漏和其他内存相关错误。
  • 安全沙箱:JVM提供了一个安全的执行环境,可以限制代码对系统资源的访问。这有助于防止恶意代码对系统造成破坏。
  • 类加载器:JVM包含一个类加载器子系统,负责动态加载、验证和准备类文件以供执行。类加载器确保类文件的完整性和安全性。
  • 本地接口:JVM提供了与本地库交互的接口(如JNI,Java Native Interface),允许Java代码调用本地代码(C/C++等),以实现特定功能或性能优化。
  • 多线程支持:JVM支持多线程执行,允许程序同时执行多个任务。
1.3. 什么是JNI?

JNI(Java Native Interface)是一个允许Java代码与本地代码(如:C/C++)进行交互的接口。通过JNI,Java应用程序可以调用本地库中的函数,也可以被本地代码调用,它是实现Java与C/C++混合编程的关键机制。
JNI主要包含以下两部分内容:

  • Java代码与本地代码交互的接口。
  • 支持JNI开发的一套开发工具,如: javah、javac等。
JNI接口的官方文档:https://docs.oracle.com/en/java/javase/21/docs/specs/jni/index.html
1.4. 环境说明

本章所有的示例代码的开发环境如下:

  • 操作系统: Ubuntu 24.04
  • JDK版本: 21.0.5
  • GCC版本: 13.3.0
  • 开发工具:VSCode
2. 开发环境搭建

2.1. Windows


  • 在官网下载最新版本的安装包,官网下载地址:https://www.oracle.com/cn/java/technologies/downloads/
    1.png

  • 双击安装包,根据提示一步步安装即可。
  • 打开命令行输入一下命令,验证是否安装成功,如果有显示相应的版本号则说明安装成功。
    1. java -version
    复制代码
2.2. Linux(Ubuntu)

安装JDK:
  1. # 1. 更新软件包列表
  2. sudo apt update
  3. # 3. 该命令将自动选择并安装最新的 LTS 版本,当前是 OpenJDK 21[5]。
  4. sudo apt install default-jdk
  5. # 3. 验证是否安装成功,如果有显示相应的版本号则说明安装成功。
  6. java --version
复制代码
设置环境变量:
  1. # 1. 查找JDK的安装路径
  2. update-alternatives --config java
  3. There are 2 choices for the alternative java (providing /usr/bin/java).
  4.   Selection    Path                                         Priority   Status
  5. ------------------------------------------------------------
  6. * 0            /usr/lib/jvm/java-21-openjdk-amd64/bin/java   2111      auto mode
  7.   1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java   1111      manual mode
  8. # 2. vim打开.zshrc(如果你的SHELL用的是.bashrc,替换成相应的.bashrc)
  9. vim ~/.zshrc
  10. # 3. 在文件末尾添加如下内容
  11. export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
  12. export PATH=${JAVA_HOME}/bin:${PATH}
  13. # 4. 重新加载配置
  14. source ~/.zshrc
复制代码
2.3. macOS

以下是通过Homebrew工具的安装步骤,确保已经安装Homebrew。
  1. # 1. 更新软件包列表
  2. brew update
  3. # 2.1 安装Oracle JDK的最新版本
  4. brew install oracle-jdk
  5. # 2.2 安装Open JDK的最新版本
  6. brew install java
  7. # 3. 验证是否安装成功,如果有显示相应的版本号则说明安装成功。
  8. java --version
复制代码
2.4. Open JDK与Oracle JDK

Java语言最初由Sun公司研发,并发布了Java SE(Standard Edition)的规范和开源的Open JDK。Sun公司后被Oracle公司收购,Oracle基于Open JDK开发了 Oracle JDK。
Open JDK是一个完全开源的项目,遵循GPL v2许可。任何人都可以下载、使用、修改和分发它的代码。主要的Linux发行版(如:Fedora,Ubuntu等)提供OpenJDK作为默认的Java SE实现。
Oracle JDK则基于Open JDK构建,但包含一些闭源组件,如Java插件、Java WebStart的实现和一些第三方组件。这些组件包括了一些商业功能,未开源。
Open JDK和Oracle JDK都遵循Java SE的规范,只是Oracle JDK提供了更多商业版的未开源的功能。
3. Say Hello程序

3.1. 新建SayHello.java

新建一个say_hello的测试目录,然后在该目录下新建一个SayHello.java文件,并编写如下代码:
  1. public class SayHello {
  2.     // 类方法
  3.     private native void sayHello(String name);
  4.     // 静态方法
  5.     private static native void sayGoodbye(String name);
  6.     static {
  7.         // 在程序初始化时加载native动态库(libhello.so)
  8.         System.loadLibrary("hello");
  9.     }
  10.     public static void main(String[] args) {
  11.         new SayHello().sayHello("Spencer");
  12.         SayHello.sayGoodbye("陌尘");
  13.     }
  14. }
复制代码
说明:

  • 这里有两个被声明为native的方法,表示这两个方法需要native代码(C/C++)实现。这里一个是普通的类成员方法,一个是静态的类方法。
    1. private native void sayHello(String name);
    2. private static native void sayGoodbye(String name);
    复制代码
  • static包含的代码块,表示在程序初始化时加载native动态库(libhello.so)
    1. static {
    2.     System.loadLibrary("hello");
    3. }
    复制代码
3.2. 编译SayHello.java
  1. javac ./SayHello.java
复制代码
执行完成后,会生成一个SayHello.class的字节码文件。
3.3. 生成SayHello.h

执行以下命令生成native代码的头文件
  1. # JDK 9.0 之前
  2. javah -cp ./ -d ./ SayHello
  3. # `-cp ./`表示设置classpath为当前目录,在当前目录下查找.class文件
  4. # `-d ./`表示设置头文件的输出目录为当前目录
  5. # JDK 9.0 及之后
  6. javac -h ./ ./SayHello.java
  7. # 第一个`./` 表示设置头文件的输出目录为当前目录
复制代码
执行成功后会在当前目录下生成SayHello.h头文件,内容如下:
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class SayHello */
  4. #ifndef _Included_SayHello
  5. #define _Included_SayHello
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     SayHello
  11. * Method:    sayHello
  12. * Signature: (Ljava/lang/String;)V
  13. */
  14. JNIEXPORT void JNICALL Java_SayHello_sayHello
  15.   (JNIEnv *, jobject, jstring);
  16. /*
  17. * Class:     SayHello
  18. * Method:    sayGoodbye
  19. * Signature: (Ljava/lang/String;)V
  20. */
  21. JNIEXPORT void JNICALL Java_SayHello_sayGoodbye
  22.   (JNIEnv *, jclass, jstring);
  23. #ifdef __cplusplus
  24. }
  25. #endif
  26. #endif
复制代码
代码说明:

  • 头文件中包含两个函数分别和.java中的两个方法一一对应。
  • 函数声明中开通的部分JNIEXPORT void JNICALL,这个与《导出接口的定义》一文中的EAPI int CALLType是不是非常类似?是的,它就是JNI提供的动态库导出接口声明和调用约定声明。
  • 函数的命名非常有规律,其实它是遵循了JNI的函数命名规范:
    1. Java_{package_name}_{class_name}_{function_name}(JNI arguments)。
    复制代码
  • 函数的参数

    • 第一个参数:JNIEnv *env是一个指向JNI运行环境的指针,提供了JNI接口的各种功能函数。
    • 第二个参数:

      • 如果是一个普通的成员方法则参数为jobject obj,指代java中的this对象,可以通过该参数来获取Java对象的方法和属性。
      • 如果是一个静态的类方法则参数为jclass cls,指代java中的类,可以通过该参数来获取Java类的静态方法和静态属性。

    • 其他参数: 按从左到右的顺序与.java中声明的方法的参数一一对应。

3.4. 实现SayHello.cp

新建SayHello.cpp文件,并实现头文件声明的两个函数,内容如下:
[code]#include "SayHello.h"#include JNIEXPORT void JNICALL Java_SayHello_sayHello(JNIEnv* env, jobject obj, jstring name){    // 将jstring转化成C风格的UTF-9字符串    const char* cName = env->GetStringUTFChars(name, nullptr);    if (cName == nullptr)    {        return;    }    std::cout
您需要登录后才可以回帖 登录 | 立即注册