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/ 頭文件目錄。
文件: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");
}
}
javac Prompt.java
產出 Prompt.class。
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。文件: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);
}
| 函數 | 作用 | 注意 |
|---|---|---|
GetStringUTFChars | Java String → const char* (UTF-8) | 必須配對 ReleaseStringUTFChars |
NewStringUTF | const char* → Java String | 內容需為合法 UTF-8 |
GetStringCritical | 獲取直接指針(性能優化) | 嚴禁在調用間阻塞/分配內存 |
FindClass | 查找類 | 返回 jclass,需 DeleteLocalRef |
GetMethodID | 獲取方法 ID | 簽名格式參考 javap -s |
CallObjectMethod | 調用實例方法 | 對應返回類型有 CallIntMethod 等 |
# 關鍵:-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 參數。# 確保動態庫在庫搜索路徑
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);
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 NULL | JVM 內存不足或字符串包含非 UTF-8 | 檢查輸入編碼,捕獲 NULL 返回 |
| 段錯誤 | C 代碼指針越界、未檢查 NULL | 加入防御性檢查,使用 valgrind 調試 |
編譯報錯 jni.h not found | JDK 頭文件路徑錯誤 | 用 find /usr -name jni.h 定位並調整 -I |
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 開發核心流程:
native 方法 + System.loadLibraryjavac 編譯 → javah -jni 生成頭文件GetStringUTFChars/NewStringUTF 等)gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux 編譯 .soLD_LIBRARY_PATH 包含 .so 目錄掌握此流程,即可在 Linux 下實現 Java 與 C/C++ 高性能互操作。生產項目建議引入 CMake + Maven/Gradle 整合,實現跨平台自動化構建。