杰瑞科技汇

native关键字如何连接Java与本地代码?

核心概念

native 是 Java 中的一个修饰符,用于声明一个方法,当一个方法被声明为 native 时,它意味着:

native关键字如何连接Java与本地代码?-图1
(图片来源网络,侵删)

这个方法的实现不是用 Java 代码编写的,而是用其他语言(最常见的是 C 或 C++)实现的,并且这些代码是平台相关的(Platform-Specific)。

native 关键字是 Java 与其他语言(主要是本地代码)进行交互的“桥梁”或“接口”。


为什么需要 native 关键字?(它的用途)

Java 的设计理念是“一次编写,到处运行”(Write Once, Run Anywhere),通过 Java 虚拟机实现了跨平台性,在某些特定场景下,纯 Java 代码无法满足需求,这时就需要 native 方法:

  1. 访问系统底层资源

    native关键字如何连接Java与本地代码?-图2
    (图片来源网络,侵删)
    • Java 被设计为一种安全的、受限制的语言,不能直接操作内存地址、硬件端口等底层资源,但有时必须这样做,
      • 硬件交互:直接与显卡、声卡、网卡等硬件通信。
      • 操作系统功能:调用操作系统的特定功能,如 Windows 的注册表操作、Linux 的某些内核特性。
      • 内存管理:进行高性能的内存操作,如 C 语言中的指针操作。
  2. 性能关键型代码

    • 对于计算密集型任务,如物理引擎、图形渲染、音视频编解码等,用 C/C++ 实现通常比 Java 快得多,可以将这些核心算法用 C/C++ 编写,然后通过 native 方法在 Java 中调用。
  3. 复用现有库

    • 很多成熟的、高性能的库(如科学计算库、加密库、数据库驱动等)都是用 C/C++ 编写的,通过 native 方法,Java 程序可以无缝地调用这些现有的本地库,而无需重新用 Java 实现一遍。
  4. 集成遗留系统

    • 在一些大型企业中,可能存在大量用 C/C++ 编写的遗留代码,为了保护投资并让新的 Java 系统能够利用这些旧代码,native 方法是一种有效的集成方式。

native 方法的特点

  1. 没有方法体

    • native 方法的声明中只有方法签名,没有用 包围的实现代码,分号 表示方法的结束。
    • public native void sayHello();
  2. 与平台相关

    • native 方法的实现代码(C/C++)需要为不同的操作系统(如 Windows, Linux, macOS)和不同的 CPU 架构(如 x86, ARM)分别编译,这意味着你的 Java 应用程序打包时,可能需要包含不同平台的本地库文件(.dll for Windows, .so for Linux, .dylib for macOS)。
  3. 性能与安全性的权衡

    • 优点:可以突破 Java 的性能限制,访问底层资源。
    • 缺点:牺牲了 Java 的跨平台性(需要为不同平台提供库),并且引入了不安全性(本地代码可以绕过 JVM 的安全机制,导致内存泄漏、段错误等问题),调试本地代码通常比调试 Java 代码更困难。

如何使用 native 方法(一个完整的例子)

使用 native 方法通常包括以下几个步骤:

步骤 1:在 Java 代码中声明 native 方法

在一个 Java 类中声明你需要的 native 方法。

// NativeDemo.java
public class NativeDemo {
    // 声明一个 native 方法
    public native void displayMessage();
    // 加载包含本地代码的库
    static {
        // System.loadLibrary() 会尝试加载名为 "NativeDemo" 的库
        // 在 Windows 上是 NativeDemo.dll,在 Linux 上是 libNativeDemo.so
        System.loadLibrary("NativeDemo");
    }
    public static void main(String[] args) {
        new NativeDemo().displayMessage();
    }
}

注意 static 代码块System.loadLibrary("NativeDemo") 用于加载本地库,JVM 会在系统路径(如 Windows 的 PATH,Linux 的 LD_LIBRARY_PATH)中查找这个库,这个调用必须在调用任何 native 方法之前执行。

步骤 2:生成 C/C++ 头文件(.h)

你需要一个工具来将 Java 类中的 native 方法声明转换为 C/C++ 的函数原型,这个工具就是 javah(在旧版 JDK 中)或 javac -h(在新版 JDK 中,推荐使用)。

  1. 首先编译 Java 文件:
    javac NativeDemo.java
  2. 然后使用 javac -h 生成头文件:
    javac -h . NativeDemo.java

    这会在当前目录下生成一个名为 com_example_NativeDemo.h 的文件(如果你的类在 com.example 包下),我们简化一下,假设没有包,生成的文件就是 NativeDemo.h

NativeDemo.h 的内容大致如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h> /* Header for JNI */
/* Header for class NativeDemo */
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeDemo
 * Method:    displayMessage
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeDemo_displayMessage
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

关键点解释

  • #include <jni.h>:JNI (Java Native Interface) 的头文件,定义了与 JVM 交互所需的所有数据结构和函数。
  • JNIEXPORT:一个宏,表示该函数可以被外部(JVM)调用。
  • JNICALL:一个宏,定义了函数的调用约定。
  • Java_NativeDemo_displayMessage:这是 C/C++ 函数的命名规则,由 包名_类名_方法名 组成,如果没有包,类名_方法名
  • (JNIEnv *, jobject):这是所有 native 方法的标准参数。
    • JNIEnv *:一个指向 JNI 环境指针的指针,通过这个指针,你可以在 C/C++ 代码中调用 JNI 函数,例如创建 Java 对象、访问 Java 字段、调用 Java 方法等。
    • jobject:一个指向 NativeDemo 对象本身的引用,如果方法是 static 的,这个参数的类型会是 jclass,代表 NativeDemoClass 对象。

步骤 3:用 C/C++ 实现本地方法

创建一个 C/C++ 文件(NativeDemo.c),并实现 NativeDemo.h 中声明的函数。

// NativeDemo.c
#include <stdio.h>
#include "NativeDemo.h" // 包含刚才生成的头文件
// 实现 Java_NativeDemo_displayMessage 函数
JNIEXPORT void JNICALL Java_NativeDemo_displayMessage(JNIEnv *env, jobject obj) {
    printf("Hello from C! This message is printed by a native method.\n");
    // 你还可以通过 env 指针来操作 Java 对象
    // 获取对象的类,然后调用其方法或访问字段
}

步骤 4:编译 C/C++ 代码为本地库

你需要使用 C/C++ 编译器(如 GCC 或 Clang)将 .c 文件编译成动态链接库。

  • 在 Linux 上 (使用 GCC):

    gcc -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -o libNativeDemo.so NativeDemo.c
    • -shared: 生成共享库(.so 文件)。
    • -fpic: 生成位置无关代码,这是共享库的要求。
    • -I: 指定 JNI 头文件的路径(${JAVA_HOME} 是你的 JDK 安装路径)。
    • -o: 指定输出的库名。
  • 在 Windows 上 (使用 MinGW/GCC):

    gcc -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -o NativeDemo.dll NativeDemo.c

步骤 5:运行 Java 程序

  1. 确保 libNativeDemo.so (Linux) 或 NativeDemo.dll (Windows) 位于系统能找到的路径中。
    • Linux: 将 .so 文件放到 /usr/libLD_LIBRARY_PATH 指定的目录。
    • Windows: 将 .dll 文件放到与 NativeDemo.class 相同的目录,或者添加到系统的 PATH 环境变量中。
  2. 运行 Java 程序:
    java NativeDemo

预期输出

Hello from C! This message is printed by a native method.

特性 描述
关键字 native
作用 声明一个方法,其实现由非 Java 语言(如 C/C++)提供。
方法体 没有,以分号 结束。
跨平台性 ,需要为不同平台编译对应的本地库(.dll, .so, .dylib)。
性能 可以实现高性能,但也引入了本地代码的复杂性。
安全性 较低,本地代码可以绕过 JVM 的安全管理器。
主要用途 访问硬件、调用系统 API、性能优化、复用现有 C/C++ 库。
核心机制 Java Native Interface (JNI),是连接 Java 和本地代码的官方规范。

native 关键字是一个强大的工具,但它是一把“双刃剑”,它为 Java 开发者打开了通往底层世界的大门,但也带来了平台依赖、安全风险和调试困难等挑战,它应该被谨慎使用,仅在必要时才被引入到 Java 项目中。

分享:
扫描分享到社交APP
上一篇
下一篇