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

JNI 開發全攻略:Java 調用 C/C++ 本地庫完整流程與實戰技巧

     編輯:Java 程式設計入門

Java Native Interface (JNI) 核心機制與工程化實踐

JNI 是 Java 與本地代碼 (C/C++/Assembly) 交互的橋樑。本文系統梳理 聲明 native 方法 → javah 生成頭文件 → C/C++ 實現 → 編譯動態庫 → Java 加載調用 全流程,並補充異常處理、引用管理、性能優化、線程附着、構建系統集成等生產級關鍵點。

標準 JNI 開發流程

  1. Java 聲明:
    public class SmsSender {
        static { System.loadLibrary("smssender"); } // 加載 libsmssender.so / smssender.dll
        public native int sendSms(String phone, String content); // 僅聲明
        public static void main(String[] args) {
            System.out.println(new SmsSender().sendSms("13800000000", "Hello JNI"));
        }
    }
    
  2. 編譯 Java:javac SmsSender.java 生成 SmsSender.class
  3. 生成頭文件:javah -jni SmsSender 生成 SmsSender.h
    /* DO NOT EDIT - machine generated */
    #include 
    #ifndef _Included_SmsSender
    #define _Included_SmsSender
    #ifdef __cplusplus
    extern "C" {
    #endif
    JNIEXPORT jint JNICALL Java_SmsSender_sendSms
      (JNIEnv *, jobject, jstring, jstring);
    #ifdef __cplusplus
    }
    #endif
    #endif
    
  4. C/C++ 實現 (SmsSender.c):
    #include "SmsSender.h"
    #include 
    JNIEXPORT jint JNICALL Java_SmsSender_sendSms
      (JNIEnv *env, jobject obj, jstring phone, jstring content) {
        // 1. 將 jstring 轉為 C 字符串
        const char *cPhone = (*env)->GetStringUTFChars(env, phone, NULL);
        const char *cContent = (*env)->GetStringUTFChars(env, content, NULL);
        // 2. 調用本地短信庫 API
        int result = sms_api_send(cPhone, cContent);
        // 3. 釋放字符串資源(必須成對調用)
        (*env)->ReleaseStringUTFChars(env, phone, cPhone);
        (*env)->ReleaseStringUTFChars(env, content, cContent);
        return result;
    }
    
  5. 編譯動態庫:
    • Linux: gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux SmsSender.c -o libsmssender.so
    • Windows: cl /LD /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" SmsSender.c /Fesmssender.dll
  6. 運行:確保庫在 java.library.path (或 LD_LIBRARY_PATH/PATH) 中,java SmsSender

核心 JNI 數據類型映射

Java 類型JNI 類型C 類型描述
booleanjbooleanunsigned char8 位
bytejbytesigned char8 位
charjcharunsigned short16 位 Unicode
shortjshortshort16 位
intjintint32_t32 位
longjlongint64_t64 位
floatjfloatfloat32 位
doublejdoubledouble64 位
Objectjobject_jobject*引用
Stringjstring_jstring*引用
int[]jintArray_jarray*引用

關鍵 JNIEnv 操作速查

// 字符串處理
const char* GetStringUTFChars(JNIEnv*, jstring, jboolean* isCopy);
void ReleaseStringUTFChars(JNIEnv*, jstring, const char*);
jstring NewStringUTF(JNIEnv*, const char*);

// 數組處理
jint* GetIntArrayElements(JNIEnv*, jintArray, jboolean*);
void ReleaseIntArrayElements(JNIEnv*, jintArray, jint*, jint mode); // mode: 0/JNI_COMMIT/JNI_ABORT
jintArray NewIntArray(JNIEnv*, jsize);
void SetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, jint*);

// 類與方法
jclass FindClass(JNIEnv*, const char*);
jmethodID GetMethodID(JNIEnv*, jclass, const char*, const char*); // 實例方法
jmethodID GetStaticMethodID(JNIEnv*, jclass, const char*, const char*);
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
jint CallIntMethod(JNIEnv*, jobject, jmethodID, ...);
void CallVoidMethod(JNIEnv*, jobject, jmethodID, ...);

// 異常處理
jthrowable ExceptionOccurred(JNIEnv*);
void ExceptionClear(JNIEnv*);
void ThrowNew(JNIEnv*, jclass, const char*);

// 引用管理
jobject NewGlobalRef(JNIEnv*, jobject); // 跨調用持久引用
void DeleteGlobalRef(JNIEnv*, jobject);
jobject NewLocalRef(JNIEnv*, jobject);  // 手動創建局部引用
void DeleteLocalRef(JNIEnv*, jobject);  // 手動釋放(防止局部引用表溢出)

生產級最佳實踐

  1. 錯誤處理:每次 JNI 調用後檢查 ExceptionOccurred(),拋出 Java 異常而非返回錯誤碼
  2. 資源管理:GetStringUTFChars 必配 ReleaseStringUTFChars;數組元素同理;使用 try/finally 模式在 C 中模擬
  3. 引用洩漏:長時間運行的本地線程需 DeleteLocalRef 清理;全局引用必須 DeleteGlobalRef 釋放
  4. 線程附着:非 Java 創建的線程調用 JNI 前必須 (*jvm)->AttachCurrentThread(jvm, &env, NULL),結束前 DetachCurrentThread
  5. 性能優化:
    • 緩存 jclass、jmethodID、jfieldID(靜態變量,初始化一次)
    • 批量操作數組:GetIntArrayRegion/SetIntArrayRegion 避免逐元素調用
    • 關鍵路徑避免字符串轉換,傳遞 byte[] 或 ByteBuffer (直接內存)
  6. 構建系統集成:CMake/Makefile/Gradle (experimental) 自動化編譯、鏈接、複製到 src/main/jniLibs/${arch}/
  7. 調試技巧:printf + fflush(stdout);GDB/LLDB attach 到 Java 進程;-Xcheck:jni 啟動參數檢測 JNI 錯誤

常見坑與解決

問題原因解決
UnsatisfiedLinkError庫未找到、方法簽名不匹配、依賴庫缺失檢查 java.library.path、javah 重新生成頭文件、ldd/Dependency Walker 檢查依賴
JNI WARNING: local reference table overflow循環中大量創建局部引用未刪除循環內調用 DeleteLocalRef 或使用 PushLocalFrame/PopLocalFrame
Crash in GetStringUTFChars傳入 null jstring調用前檢查 jstring != NULL
多線程 Crash非附着線程使用 JNIEnvAttachCurrentThread 獲取專屬 JNIEnv
內存洩漏未 Release 數組/字符串、未 DeleteGlobalRef嚴格配對、使用 RAII 封裝類

現代替代方案

  • JNA (Java Native Access):純 Java 調用本地庫,無需編寫 C++ Glue 代碼,開發效率高,性能略低於 JNI
  • JNR-FFI:基於 invokedynamic,性能接近 JNI,API 更友好
  • GraalVM Native Image:將 Java 編譯為本地可執行文件,直接鏈接 C 庫,無 JNI 開銷
  • Panama Project (JDK 19+):Foreign Function & Memory API,標準化、安全、高性能的本地互操作

總結

JNI 是 Java 突破沙箱、復用 C/C++ 生態、實現高性能計算的關鍵技術。掌握 類型映射、引用管理、異常傳播、線程模型、構建集成 是編寫穩健本地庫的基礎。新項目優先考慮 JNA 或 Panama API 降低複雜度;性能極度敏感、需精細控制內存佈局、對接現有 C++ 庫時,JNI 仍是最佳選擇。

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