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

JNI 快速入門教程:從 Java 調用 C/C++ 實戰演示

     編輯:Java 綜合教學

什麼是 JNI?

**JNI (Java Native Interface)** 是 Java 與本地代碼(C/C++)交互的橋樑。當 Java 難以勝任底層系統操作、高性能計算、硬體控制或需復用現有 C/C++ 庫時,JNI 允許 Java 代碼調用本地方法,極大擴展了 Java 的應用邊界。

完整開發流程:HelloWorld 實戰

目標:Java 傳入字串 `"world"`,C 函數在前面拼接 `"hello "` 並返回 `"hello world"`。

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

public class Test {
    // 1. native 關鍵字聲明本地方法
    public native String helloworld(String text);

    public static void main(String[] args) {
        // 2. 載入動態庫:Windows 對應 Test2.dll,Linux 對應 libTest2.so
        System.loadLibrary("Test2");
        Test ts = new Test();
        String result = ts.helloworld("world");
        System.out.println(result); // 輸出: hello world
    }
}

步驟 2:編譯 Java 源文件

> javac Test.java
// 生成 Test.class

步驟 3:生成 C/C++ 頭文件

> javah Test
// 生成 Test.h

生成的 `Test.h` 關鍵內容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Test */
#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    helloworld
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_Test_helloworld
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
  • 函數命名規範:`Java_包名_類名_方法名`(此處無包名)。
  • 參數:`JNIEnv*`(JNI 環境指針,提供操作 JVM 的函數表)、`jobject`(調用對象,此處為 `Test` 實例)、`jstring`(Java 字串參數)。
  • 返回:`jstring`(Java 字串)。

步驟 4:編寫 C 實現文件

#include "Test.h"
#include <string.h>

JNIEXPORT jstring JNICALL Java_Test_helloworld
  (JNIEnv *env, jobject obj, jstring j_str) {
    
    // 1. 將 Java 字串 (UTF-16) 轉為 C 字串 (UTF-8)
    const char* c_str = (*env)->GetStringUTFChars(env, j_str, 0);
    
    // 2. 構建結果字串
    char buffer[128] = "hello "; // 預留空間
    strcat(buffer, c_str);       // 拼接
    
    // 3. 釋放 GetStringUTFChars 分配的內存(關鍵!防內存洩漏)
    (*env)->ReleaseStringUTFChars(env, j_str, c_str);
    
    // 4. 將 C 字串轉為 Java 字串返回
    return (*env)->NewStringUTF(env, buffer);
}

關鍵 JNI 字串函數對照表:

函數用途注意事項
`GetStringUTFChars`Java String → C `const char*` (UTF-8)必須配對調用 `ReleaseStringUTFChars` 釋放
`ReleaseStringUTFChars`釋放上述分配的內存忘記調用導致 Native 內存洩漏
`NewStringUTF`C `const char*` (UTF-8) → Java String返回局部引用,若長期持有需轉全局引用

步驟 5:編譯生成動態庫

Windows (MinGW/GCC):

> gcc -I "C:\Program Files\Java\jdk1.8.0_45\include" \
        -I "C:\Program Files\Java\jdk1.8.0_45\include\win32" \
        -shared Test2.c -o Test2.dll
  • -I 指定 JDK `include` 與 `include/win32` 目錄(含 `jni.h`、`jni_md.h`)。
  • -shared 生成動態鏈接庫 (DLL)。

Linux (GCC):

$ gcc -I /usr/lib/jvm/java-8-openjdk/include \
        -I /usr/lib/jvm/java-8-openjdk/include/linux \
        -fPIC -shared Test2.c -o libTest2.so
  • -fPIC 生成位置無關代碼(共享庫必需)。
  • 輸出命名慣例 `lib.so`,`System.loadLibrary("Test2")` 會自動映射。

步驟 6:運行驗證

> java Test
// 輸出: hello world

核心知識點總結

  1. 四步走流程:Java 類 → `javac` → `javah` 生成 .h → C/C++ 實現 → 編譯為動態庫 → `System.loadLibrary` 載入 → 調用。
  2. 數據類型映射:Java 基本類型對應 `jint`、`jdouble` 等;物件類型對應 `jstring`、`jobject`、`jarray` 等;引用類型分**局部引用**(自動釋放)、**全局引用**(手動管理)、**弱全局引用**。
  3. 異常處理:Native 代碼拋出異常需用 `ThrowNew`,Java 端 `try-catch` 捕獲。
  4. 線程安全:`JNIEnv*` 線程私有,**不可跨線程傳遞**;多線程需用 `JavaVM*` 獲取當前線程 `JNIEnv`。
  5. 性能考量:JNI 調用有開銷(上下文切換、類型轉換),高頻調用需權衡;大數據傳輸考慮 `GetPrimitiveArrayCritical` / `DirectByteBuffer` 零拷貝。

常見坑與避雷指南

  • UnsatisfiedLinkError:庫名錯誤(`loadLibrary` 不加前綴/後綴)、庫未在 `java.library.path`、依賴庫缺失、32/64 位不匹配。
  • 內存洩漏:忘記 `ReleaseStringUTFChars`、`DeleteLocalRef`、`DeleteGlobalRef`。
  • 崩潰:Native 代碼段錯誤(空指針、越界)直接導致 JVM Crash,需用調試器(GDB、Visual Studio)定位。
  • 編碼問題:統一使用 UTF-8(`GetStringUTFChars`/`NewStringUTF`),避免平台預設編碼不一致。
  • 現代替代方案

    對於新項目,優先考慮:

    • JNA (Java Native Access):純 Java 調用本地庫,無需寫 C/C++ 粘合代碼,開發效率高。
    • JNR-FFI:更現代、性能更好的外部函數介面庫。
    • GraalVM Native Image / Polyglot:原生編譯或多語言互操作。

    JNI 仍是理解 Java 底層互操作的基石,掌握其原理有助於排查疑難雜症與評估技術選型。

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