杰瑞科技汇

eclipse c 调用java

  • 性能优化:用 C 实现计算密集型或对性能要求极高的部分,用 Java 实现业务逻辑和界面。
  • 硬件交互:通过 C 库直接与操作系统或硬件设备通信。
  • 复用现有库:利用已有的、只有源码或二进制形式的 C/C++ 库。

实现 C 调用 Java 的核心技术是 Java Native Interface (JNI),下面我将提供一个详细的、分步的指南,教你在 Eclipse 中如何操作。

eclipse c 调用java-图1
(图片来源网络,侵删)

核心概念:JNI

JNI 是 Java 平台的一部分,它定义了一套规范,允许 Java 代码和其他语言(主要是 C/C++)进行交互,对于 C 调用 Java,流程如下:

  1. Java 端:定义一个 native 方法(即一个声明但不提供具体实现的方法,告诉 JVM 这个方法由外部代码实现)。
  2. C 端:编写 C 代码来实现这个 native 方法,C 代码需要包含 JNI 的头文件,并使用 JNI 提供的 API 来操作 Java 对象、调用 Java 方法等。
  3. 桥梁:JVM 在运行时会加载 C 实现的库(在 Windows 上是 .dll,Linux 上是 .so,macOS 上是 .dylib),并将 Java 方法的调用“转发”给 C 函数。

详细步骤:Eclipse CDT 环境下 C 调用 Java

我们将创建一个简单的示例:

  • Java 程序有一个 native 方法 printHelloFromJava()
  • C 程序调用这个方法,并在控制台打印出从 Java 返回的字符串。

第 0 步:准备工作

  1. 安装 Eclipse:确保你安装了 Eclipse IDE for C/C++ Developers (CDT 版本),如果你只有 Eclipse for Java Developers,也可以通过 Help -> Install New Software -> "Eclipse CDT" 来安装 CDT 插件。
  2. 安装 JDK:确保你的系统已经安装了 JDK,JAVA_HOME 环境变量配置正确,Eclipse 需要用它来找到 javac, javah (在 JDK 10+ 中被 javac -h 替代) 等工具。

第 1 步:创建 Java 项目并定义 native 方法

  1. 在 Eclipse 中,创建一个新的 Java Project (File -> New -> Java Project),命名为 JNITestJava
  2. src 目录下创建一个 Java 类,com.example.NativeCaller
  3. NativeCaller.java 中编写代码,定义一个 native 方法。

com/example/NativeCaller.java

package com.example;
public class NativeCaller {
    // 1. 声明一个 native 方法
    // 注意:必须是 public 或 protected,并且是 static 或非 static 的
    public native void printHelloFromJava();
    // 为了方便加载库,添加一个静态代码块
    static {
        // 2. 加载包含 C 实现的动态链接库
        // System.loadLibrary 会根据操作系统自动寻找 libNativeCaller.so (Linux) 或 NativeCaller.dll (Windows)
        System.loadLibrary("NativeCaller");
    }
    public static void main(String[] args) {
        System.out.println("Java: 程序启动,准备调用 C 代码。");
        NativeCaller caller = new NativeCaller();
        // 3. 在 Java 中调用 native 方法
        // 这会触发 C 代码的执行
        caller.printHelloFromJava();
        System.out.println("Java: C 代码调用完毕。");
    }
}

第 2 步:生成 C 头文件 (.h)

这是连接 Java 和 C 的关键一步,我们需要根据 Java 类的 native 方法声明,生成一个 C 头文件,这个头文件包含了 JNI 需要的函数签名。

eclipse c 调用java-图2
(图片来源网络,侵删)
  1. 编译 Java 项目:右键点击项目 -> Build Project。
  2. 打开命令行(或使用 Eclipse 自带的终端 Terminal),切换到 Java 项目的根目录(即 JNITestJava 目录)。
  3. 运行 javacjavah (或 javac -h) 命令。

对于 JDK 8 及更早版本 (使用 javah):

# 1. 编译 .java 文件
javac -d . src/com/example/NativeCaller.java
# 2. 生成 .h 文件
# -classpath . 指定类文件所在目录
# -jni 表示生成 JNI 标准的头文件
# com.example.NativeCaller 是完整的类名
javah -classpath . -jni com.example.NativeCaller

对于 JDK 10 及更新版本 (使用 javac -h):

# 1. 编译 .java 文件,并直接生成头文件到 include 目录
# -h 指定头文件输出目录
javac -d . -h include src/com/example/NativeCaller.java

执行后,你会在项目根目录下(或你指定的 include 目录下)找到一个头文件:com_example_NativeCaller.h

com_example_NativeCaller.h (示例内容)

eclipse c 调用java-图3
(图片来源网络,侵删)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_NativeCaller */
#ifndef _Included_com_example_NativeCaller
#define _Included_com_example_NativeCaller
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_NativeCaller
 * Method:    printHelloFromJava
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_NativeCaller_printHelloFromJava
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

这个文件非常重要!它定义了你的 C 函数必须使用的精确名称 Java_com_example_NativeCaller_printHelloFromJava 和参数 (JNIEnv *, jobject)

第 3 步:创建 C/C++ 项目并实现 C 代码

我们使用 Eclipse CDT 来创建 C 项目并实现这个函数。

  1. 在 Eclipse 中,创建一个新的 C Project (File -> New -> C Project),命名为 JNITestC
  2. 在 "Project type" 中选择 "Empty Project"。
  3. 创建完成后,右键点击项目 -> New -> Source File,命名为 NativeCaller.c
  4. 将上一步生成的 com_example_NativeCaller.h 文件复制到 JNITestC 项目的根目录下。
  5. NativeCaller.c 中包含这个头文件,并实现函数。

NativeCaller.c

#include <stdio.h>
#include "com_example_NativeCaller.h" // 包含我们生成的头文件
// 函数名必须和头文件中定义的完全一样
// 参数也必须一样: JNIEnv* 和 jobject
JNIEXPORT void JNICALL Java_com_example_NativeCaller_printHelloFromJava(JNIEnv *env, jobject obj) {
    // JNI API 使用示例
    // 1. 获取调用该方法的 Java 对象的 Class 对象
    jclass clazz = (*env)->GetObjectClass(env, obj);
    // 2. 查找要调用的 Java 方法的 ID
    // 我们想调用 java.lang.System.out.println(String)
    // 首先找到 System.out 对象
    jclass system_class = (*env)->FindClass(env, "java/lang/System");
    jfieldID out_field = (*env)->GetStaticFieldID(env, system_class, "out", "Ljava/io/PrintStream;");
    jobject out_obj = (*env)->GetStaticObjectField(env, system_class, out_field);
    // 然后找到 println 方法
    jclass print_stream_class = (*env)->FindClass(env, "java/io/PrintStream");
    jmethodID println_method = (*env)->GetMethodID(env, print_stream_class, "println", "(Ljava/lang/String;)V");
    // 3. 创建一个 Java 字符串对象
    jstring java_message = (*env)->NewStringUTF(env, "Hello from C! I am calling Java's println method.");
    // 4. 调用 Java 的 println 方法
    (*env)->CallVoidMethod(env, out_obj, println_method, java_message);
    // 5. 释放本地引用 (最佳实践)
    (*env)->DeleteLocalRef(env, java_message);
    (*env)->DeleteLocalRef(env, out_obj);
    (*env)->DeleteLocalRef(env, print_stream_class);
    (*env)->DeleteLocalRef(env, system_class);
    (*env)->DeleteLocalRef(env, clazz);
    printf("C: Java native method has been called.\n");
}

第 4 步:配置 C 项目以编译成动态库

C 代码需要被编译成动态链接库,Java 才能加载它。

  1. 右键点击 JNITestC 项目 -> Properties -> C/C++ Build -> Settings。
  2. Tool Settings 标签页下:
    • GCC C Compiler -> Includes:
      • Include paths (-I) 中,添加 JDK 的 include 目录路径。C:/Program Files/Java/jdk-11.0.12/include (Windows) 或 /usr/lib/jvm/java-11-openjdk-amd64/include (Linux)。
      • 非常重要:如果你的系统是 64 位的,但 JDK 是 32 位的(或反之),你需要添加对应平台的子目录,如 win32linuxC:/Program Files/Java/jdk-11.0.12/include/win32
    • GCC C Linker -> Libraries:
      • Library search path (-L) 中,添加 JDK 的 lib 目录路径。C:/Program Files/Java/jdk-11.0.12/lib/usr/lib/jvm/java-11-openjdk-amd64/lib
    • GCC C Linker -> Miscellaneous:
      • Linker flags 中,输入 -ljvm,这会告诉链接器去链接 jvm 库。
  3. 应用并关闭

右键点击 JNITestC 项目 -> Build Project,如果配置正确,它会在你的项目目录下(通常是 DebugRelease 文件夹)生成动态库文件:

  • Windows: NativeCaller.dll
  • Linux: libNativeCaller.so
  • macOS: libNativeCaller.dylib

第 5 步:运行 Java 程序并加载 C 库

这是最后一步,也是最容易出现问题的一步。

  1. 复制动态库:将上一步生成的动态库文件(如 NativeCaller.dlllibNativeCaller.so)复制到 Java 项目 JNITestJava 的根目录下。
  2. 配置 Java 运行时库路径:为了让 Java 虚拟机能找到这个动态库,你需要设置 java.library.path 系统属性。
    • 方法一(推荐):修改 Java 项目的运行配置。
      • 右键点击 JNITestJava 项目 -> Run As -> Run Configurations...
      • 选择你的 Java Application。
      • Arguments 标签页的 VM arguments 文本框中输入:
        -Djava.library.path="${workspace_loc:JNITestJava}/Debug"

        (如果你的库在 Release 文件夹,就换成 Release${workspace_loc:...} 是 Eclipse 宏,代表项目在 workspace 中的绝对路径)。

    • 方法二:在代码中设置(不推荐,因为路径可能不固定)。
      System.setProperty("java.library.path", "path/to/your/library");
  3. 运行:右键点击 JNITestJava 项目中的 NativeCaller.java -> Run As -> Java Application。

预期输出

如果一切顺利,你会在 Eclipse 的 Console 看到类似下面的输出:

Java: 程序启动,准备调用 C 代码。
Hello from C! I am calling Java's println method.
C: Java native method has been called.
Java: C 代码调用完毕。

常见问题与解决方法

  1. java.lang.UnsatisfiedLinkError: no NativeCaller in java.library.path

    • 原因:JVM 找不到名为 NativeCaller 的动态库。
    • 解决
      • 确认动态库文件名是否正确(如 libNativeCaller.soNativeCaller.dll)。
      • 确认 java.library.path 是否指向了包含该动态库的正确目录。
      • 确认动态库文件是否确实存在于该目录中。
  2. java.lang.UnsatisfiedLinkError: /path/to/libNativeCaller.so: libjvm.so: cannot open shared object file: No such file or directory

    • 原因:C 库在运行时需要链接 jvm 库,但找不到它。
    • 解决:在 C 项目的 Linker 设置中,确保 -ljvm 被正确添加,Library search path 指向了 JDK 的 lib 目录。
  3. UnsatisfiedLinkError: ... printHelloFromJava ' (truncated) 'Wrong name'

    • 原因:C 函数的名称与 javah 生成的头文件中的签名不匹配。
    • 解决:仔细检查 C 函数名 Java_com_example_NativeCaller_printHelloFromJava 是否完全正确,包括包名、类名和方法名的大小写和下划线。
  4. 找不到 jni.h

    • 原因:C 编译器找不到 JNI 的头文件。
    • 解决:在 C 项目的 Compiler Includes 设置中,正确添加了 JDK 的 include 目录路径。
  5. 64 位/32 位不匹配

    • 原因:你的 Eclipse/Java 是 64 位的,但 C 编译器默认配置为 32 位(或反之),这会导致库无法加载。
    • 解决:确保所有组件(Eclipse, JDK, CDT/GCC)的位数一致,在 C 项目的 Build Configurations 中,可以设置编译器为 32 位或 64 位模式(添加 -m32-m64 标志)。

通过以上步骤,你就可以在 Eclipse CDT 环境下成功实现 C 语言调用 Java 代码了,JNI 功能非常强大,但也相对复杂,尤其是在处理数据类型、异常和内存管理时,需要多加练习。

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