首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Ubuntu 下 JNI 開發完整實戰:從 Java 聲明到 C 實現與動態庫加載

     編輯:Java 相關知識

前言

Java Native Interface (JNI) 讓 Java 調用 C/C++ 代碼,適用於性能敏感模塊、硬件交互、 legado 庫復用。本文在 Ubuntu 環境下,通過一個完整的控制台交互示例,演示從 Java 聲明 native 方法、生成頭文件、C 實現、編譯動態庫到運行驗證的全流程。

環境準備

# 安裝 JDK 與構建工具
sudo apt update
sudo apt install openjdk-11-jdk-headless gcc make

# 驗證
java -version
gcc --version

假設 JDK 安裝路徑為 /usr/lib/jvm/java-11-openjdk-amd64,包含 include/ 與 include/linux/ 頭文件目錄。

步驟 1:編寫 Java 類聲明 native 方法

文件:Prompt.java

public class Prompt {
    // 聲明 native 方法
    private native String getLine(String prompt);

    public static void main(String[] args) {
        Prompt p = new Prompt();
        String input = p.getLine("Type a line: ");
        System.out.println("User typed: " + input);
    }

    // 靜態塊加載動態庫:系統會查找 java.library.path 下的 libPrompt.so
    static {
        System.loadLibrary("Prompt");
    }
}

步驟 2:編譯生成 .class 文件

javac Prompt.java

產出 Prompt.class。

步驟 3:生成 JNI 頭文件

javah -jni Prompt

產出 Prompt.h,關鍵內容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern "C" {
#endif

// 方法簽名:(Ljava/lang/String;)Ljava/lang/String;
JNIEXPORT jstring JNICALL Java_Prompt_getLine
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
  • 命名規則:Java_包名_類名_方法名,包名用下劃線分隔,無包名則直接類名。
  • 簽名 (Ljava/lang/String;)Ljava/lang/String; 表示:參數一個 String,返回 String。

步驟 4:編寫 C 實現

文件:Prompt.c

#include 
#include 
#include "Prompt.h"

JNIEXPORT jstring JNICALL Java_Prompt_getLine
  (JNIEnv *env, jobject obj, jstring prompt) {

    // 1. 將 Java String 轉為 C 字符串 (UTF-8)
    const char *str = (*env)->GetStringUTFChars(env, prompt, NULL);
    if (str == NULL) return NULL;  // 內存不足

    // 2. 輸出提示信息
    printf("%s", str);

    // 3. 釋放 JNI 字符串引用(必須成對調用)
    (*env)->ReleaseStringUTFChars(env, prompt, str);

    // 4. 讀取用戶輸入(簡單示例,實際應處理緩衝區溢出)
    char buf[128];
    scanf("%127s", buf);  // 防止緩衝區溢出

    // 5. 將 C 字符串轉為 Java String 並返回
    return (*env)->NewStringUTF(env, buf);
}

JNI 關鍵函數速查

函數作用注意
GetStringUTFCharsJava String → const char* (UTF-8)必須配對 ReleaseStringUTFChars
NewStringUTFconst char* → Java String內容需為合法 UTF-8
GetStringCritical獲取直接指針(性能優化)嚴禁在調用間阻塞/分配內存
FindClass查找類返回 jclass,需 DeleteLocalRef
GetMethodID獲取方法 ID簽名格式參考 javap -s
CallObjectMethod調用實例方法對應返回類型有 CallIntMethod 等

步驟 5:編譯生成動態庫

# 關鍵:-shared 生成 .so,-fPIC 位置無關代碼,-I 指定 JDK 頭文件路徑
gcc -shared -fPIC \
    -I/usr/lib/jvm/java-11-openjdk-amd64/include \
    -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux \
    Prompt.c -o libPrompt.so
  • 輸出文件必須命名為 libPrompt.so,對應 System.loadLibrary("Prompt")。
  • 若報錯 jni.h: No such file,請確認 JDK 安裝路徑並調整 -I 參數。

步驟 6:運行驗證

# 確保動態庫在庫搜索路徑
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
java Prompt

預期輸出

Type a line: Hello JNI
User typed: Hello JNI

進階:包名、多參數、異常處理

帶包名的類

// Java
package com.example.jni;
public class Calculator {
    native int add(int a, int b);
    static { System.loadLibrary("Calculator"); }
}
// 生成的頭文件函數名
JNIEXPORT jint JNICALL Java_com_example_jni_Calculator_add
  (JNIEnv *, jobject, jint, jint);

C 實現拋出 Java 異常

JNIEXPORT jint JNICALL Java_com_example_jni_Calculator_divide
  (JNIEnv *env, jobject obj, jint a, jint b) {
    if (b == 0) {
        jclass exc = (*env)->FindClass(env, "java/lang/ArithmeticException");
        (*env)->ThrowNew(env, exc, "Division by zero");
        return 0;
    }
    return a / b;
}

常見錯誤與排查

錯誤現象原因解決
UnsatisfiedLinkError: no Prompt in java.library.path動態庫未在搜索路徑設置 LD_LIBRARY_PATH 或 -Djava.library.path=.
UnsatisfiedLinkError: ... undefined symbol: Java_Prompt_getLine函數名不匹配核對 javah 生成的頭文件與 C 實現簽名完全一致
JNI ERROR: GetStringUTFChars returns NULLJVM 內存不足或字符串包含非 UTF-8檢查輸入編碼,捕獲 NULL 返回
段錯誤C 代碼指針越界、未檢查 NULL加入防御性檢查,使用 valgrind 調試
編譯報錯 jni.h not foundJDK 頭文件路徑錯誤用 find /usr -name jni.h 定位並調整 -I

構建自動化:使用 CMake(推薦)

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(PromptJNI LANGUAGES C)

find_package(Java REQUIRED)
find_package(JNI REQUIRED)

add_library(Prompt SHARED Prompt.c)
target_include_directories(Prompt PRIVATE ${JNI_INCLUDE_DIRS})
set_target_properties(Prompt PROPERTIES
    PREFIX ""          # 去掉 lib 前綴?Linux 保留 lib
    SUFFIX ".so"
    OUTPUT_NAME "Prompt"
)

構建:

mkdir build && cd build
cmake ..
make

總結

JNI 開發核心流程:

  1. Java 聲明 native 方法 + System.loadLibrary
  2. javac 編譯 → javah -jni 生成頭文件
  3. C 實現 JNIEXPORT 函數,處理類型轉換(GetStringUTFChars/NewStringUTF 等)
  4. gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux 編譯 .so
  5. 運行時確保 LD_LIBRARY_PATH 包含 .so 目錄

掌握此流程,即可在 Linux 下實現 Java 與 C/C++ 高性能互操作。生產項目建議引入 CMake + Maven/Gradle 整合,實現跨平台自動化構建。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved