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

Java JNI 開發完整指南:呼叫 C/C++ 本地庫、資料類型映射與實戰流程

     編輯:Java 相關知識

JNI 架構總覽


Java 代碼          JNI 層              C/C++ 實現
┌─────────────┐    ┌──────────────┐    ┌────────────────┐
│ native 方法 │───▶│ JNIEnv 接口  │───▶│ .dll / .so 庫  │
│ System.load │    │ 類/方法簽名  │    │ 具體邏輯實現   │
└─────────────┘    └──────────────┘    └────────────────┘

Java 端開發步驟

1. 宣告 native 方法並載入庫

public class NativeDemo {
    // 靜態塊載入庫 (無副檔名,跨平台)
    static {
        System.loadLibrary("nativeDemo"); // Windows: nativeDemo.dll, Linux: libnativeDemo.so
        // 或指定絕對路徑: System.load("/path/libnativeDemo.so");
    }
    
    // 宣告 native 方法 (僅簽名,無實現)
    public native static int getValue();
    public native static void setValue(int value);
    public native String processData(String input); // 實例方法
    
    public static void main(String[] args) {
        NativeDemo demo = new NativeDemo();
        demo.setValue(42);
        System.out.println("Value: " + demo.getValue());
        System.out.println("Processed: " + demo.processData("Hello JNI"));
    }
}

2. 編譯產生 .class

javac NativeDemo.java

3. 生成 C/C++ 標頭檔

# JDK 8-10
javah NativeDemo

# JDK 10+ (javah 移除,用 javac -h)
javac -h . NativeDemo.java

生成的標頭檔 (NativeDemo.h)

/* DO NOT EDIT - machine generated */
#include 

#ifndef _Included_NativeDemo
#define _Included_NativeDemo

#ifdef __cplusplus
extern "C" {
#endif

// 靜態方法: Java_類全名_方法名
JNIEXPORT jint JNICALL Java_NativeDemo_getValue
  (JNIEnv *, jclass);

// 靜態方法帶參數
JNIEXPORT void JNICALL Java_NativeDemo_setValue
  (JNIEnv *, jclass, jint);

// 實例方法
JNIEXPORT jstring JNICALL Java_NativeDemo_processData
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

C/C++ 端實現

資料類型映射表

Java 類型JNI 類型C/C++ 類型備註
booleanjbooleanunsigned char8 位
bytejbytesigned char8 位
charjcharunsigned short16 位 Unicode
shortjshortshort16 位
intjintint32 位
longjlonglong long / int64_t64 位
floatjfloatfloat32 位
doublejdoubledouble64 位
Stringjstring不透明引用需 JNI 函數轉換
Objectjobject不透明引用所有物件基類
Classjclassjobject 子類類物件
int[]jintArrayjobject 子類陣列類型

實現檔案

#include "NativeDemo.h"
#include 
#include 

// 全域狀態 (對應 Java static 欄位)
static int g_value = 0;

// 靜態方法: 參數 (JNIEnv*, jclass)
JNIEXPORT jint JNICALL Java_NativeDemo_getValue(JNIEnv *env, jclass cls) {
    return g_value;
}

JNIEXPORT void JNICALL Java_NativeDemo_setValue(JNIEnv *env, jclass cls, jint value) {
    g_value = value;
}

// 實例方法: 參數 (JNIEnv*, jobject, ...)
JNIEXPORT jstring JNICALL Java_NativeDemo_processData(JNIEnv *env, jobject obj, jstring input) {
    // 1. jstring → C 字串 (UTF-8)
    const char *cstr = (*env)->GetStringUTFChars(env, input, NULL);
    if (cstr == NULL) return NULL; // OOM
    
    // 2. 業務邏輯
    char buffer[256];
    snprintf(buffer, sizeof(buffer), "Processed: %s", cstr);
    
    // 3. 釋放 C 字串資源
    (*env)->ReleaseStringUTFChars(env, input, cstr);
    
    // 4. C 字串 → jstring
    return (*env)->NewStringUTF(env, buffer);
}

編譯生成動態庫

Windows (MSVC)

cl /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" /LD NativeDemo.c /Fe nativeDemo.dll

Linux/macOS (GCC/Clang)

gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
    NativeDemo.c -o libnativeDemo.so

庫檔案放置

  • Java 類同目錄
  • 或 java.library.path 指定目錄:java -Djava.library.path=/path/to/lib NativeDemo
  • 或 System.load("/absolute/path/libnativeDemo.so")

進階主題

陣列操作

// Java: native int sumArray(int[] arr);
JNIEXPORT jint JNICALL Java_NativeDemo_sumArray(JNIEnv *env, jclass cls, jintArray arr) {
    jint *elements = (*env)->GetIntArrayElements(env, arr, NULL);
    jsize len = (*env)->GetArrayLength(env, arr);
    
    jint sum = 0;
    for (int i = 0; i < len; i++) sum += elements[i];
    
    (*env)->ReleaseIntArrayElements(env, arr, elements, 0); // 0: 複製回並釋放
    return sum;
}

回調 Java 方法

// 獲取類引用
jclass cls = (*env)->GetObjectClass(env, obj);
// 獲取方法 ID
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
// 呼叫
(*env)->CallVoidMethod(env, obj, mid, 42);

異常處理

// 拋出 Java 異常
jclass excCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
(*env)->ThrowNew(env, excCls, "Invalid input from native code");

建構系統整合

Maven (maven-nar-plugin)

<plugin>
    <groupId>com.github.maven-nar</groupId>
    <artifactId>maven-nar-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <libraries>
            <library>
                <type>jni</type>
            </library>
        </libraries>
    </configuration>
</plugin>

Gradle (native 套件)

plugins { id 'cpp-library' }
library {
    targetMachines.add(machines.linux.x86_64, machines.windows.x86_64, machines.mac.x86_64)
    // JNI 專用配置...
}

現代替代方案對比

方案優點缺點適用場景
JNI標準、效能最高、完全控制複雜、易崩潰、跨平台編譯麻煩高效能、現有 C/C++ 庫、硬體驅動
JNA純 Java、無需編譯 C、易用效能較 JNI 低 10-100x快速原型、系統 API 呼叫、非核心路徑
JNR-FFI效能接近 JNI、API 現代社群較小需平衡效能與開發效率
GraalVM Native ImageJava 直接編譯原生、互操作性強編譯慢、限制多雲原生、Serverless、微服務
Panama (JDK 19+)Foreign Function & Memory API、標準化預覽/孵化階段未來標準、替代 JNI/JNA

總結

JNI 是 Java 與本地代碼橋樑,核心流程:native 宣告 → javah 生成頭文件 → C/C++ 實現 → 編譯動態庫 → System.loadLibrary 載入。關鍵難點在於資料類型映射、字串/陣列轉換、記憶體管理、異常傳遞。現代開發優先評估 JNA/Panama,僅在極致效能或現有 C/C++ 資產整合時使用 JNI。理解 JNI 內部機制,有助於排查 JVM 崩潰、記憶體洩漏、效能抖動等疑難雜症。

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