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

JNI 深度實戰:原理詳解與 C++ 交互完整流程

     編輯:Java 相關知識

JNI 核心價值

JNI (Java Native Interface) 是 Java 平台的關鍵特性,彌補了「跨平台」帶來的平台無關性限制,讓 Java 能調用 C/C++ 靜態庫,發揮原生效能、復用既有代碼、存取硬體資源。

JNI 工作流程圖解

JNI 知識圖譜

  1. Java 類宣告 native 方法
  2. javac 編譯生成 .class
  3. javah -jni 生成 C/C++ 標頭檔
  4. C/C++ 實現標頭檔中的函數
  5. 編譯生成共享庫(.so/.dll/.dylib)
  6. Java 透過 System.loadLibrary 載入庫並調用

完整 HelloWorld 實戰

1. Java 端

package com.magc.jni;

public class HelloWorld {
    static {
        System.loadLibrary("Hello"); // 載入 libHello.so / Hello.dll
    }

    public native void displayHello(); // 原生方法宣告

    public static void main(String[] args) {
        new HelloWorld().displayHello();
    }
}

2. 編譯生成類檔

cd src
javac ./com/magc/jni/HelloWorld.java

3. 生成 JNI 標頭檔

javah -jni com.magc.jni.HelloWorld

產出 com_magc_jni_HelloWorld.h:

/* DO NOT EDIT - machine generated */
#include <jni.h>
#ifndef _Included_com_magc_jni_HelloWorld
#define _Included_com_magc_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_magc_jni_HelloWorld_displayHello
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

關鍵點:函數名 Java_包名_類名_方法名 嚴格對應,JNIEnv* 提供 JNI 函數表指標,jobject 對應 Java 這個實例(this)。

4. C++ 實現

#include <jni.h>
#include "com_magc_jni_HelloWorld.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_com_magc_jni_HelloWorld_displayHello
  (JNIEnv *env, jobject obj) {
    printf("From jni_helloworldImpl.cpp :");
    printf("Hello world ! \n");
    return;
}

5. 編譯生成共享庫

Linux (Ubuntu):

g++ -shared -fPIC -I /usr/lib/jvm/java-6-openjdk/include \
    -I /usr/lib/jvm/java-6-openjdk/include/linux \
    jni_helloworldImpl.cpp -o libHello.so

Windows (MinGW):

g++ -shared -I "C:\Program Files\Java\jdk\include" \
    -I "C:\Program Files\Java\jdk\include\win32" \
    jni_helloworldImpl.cpp -o Hello.dll

macOS:

g++ -dynamiclib -I /Library/Java/JavaVirtualMachines/jdk/Contents/Home/include \
    -I /Library/Java/JavaVirtualMachines/jdk/Contents/Home/include/darwin \
    jni_helloworldImpl.cpp -o libHello.jnilib

6. 執行驗證

java -Djava.library.path=. com.magc.jni.HelloWorld

輸出:

From jni_helloworldImpl.cpp :Hello world !

進階關鍵技術點

JNIEnv* —— JNI 函數表指標

  • 每個線程獨有,不可跨線程傳遞
  • 提供 200+ 操作函數:FindClass、GetMethodID、CallObjectMethod、NewStringUTF、GetStringUTFChars 等

資料類型對應表

Java 類型JNI 類型C/C++ 類型
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint / int32_t
longjlonglong long / int64_t
floatjfloatfloat
doublejdoubledouble
Stringjstringopaque
Objectjobjectopaque
int[]jintArrayopaque

字串處理陷阱

// 錯誤:直接用 GetStringUTFChars 取得指標後未 Release
const char* cstr = env->GetStringUTFChars(jstr, NULL);
// ... 使用 cstr
env->ReleaseStringUTFChars(jstr, cstr); // 必須釋放!

// 正確:使用 GetStringCritical / ReleaseStringCritical (較快,但期間不能阻塞)
// 或 NewStringUTF 直接建立新 jstring 回傳

異常處理

// C++ 端拋出 Java 異常
jclass excClass = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(excClass, "Invalid input from JNI");

// Java 端捕獲
try {
    nativeMethod();
} catch (IllegalArgumentException e) { ... }

效能優化建議

  1. 減少 JNI 邊界跨越:批量傳遞陣列而非逐元素調用
  2. 使用關鍵區 (GetPrimitiveArrayCritical) 直接存取陣列記憶體(需極短時間內釋放)
  3. 快取 jclass/jmethodID/jfieldID:FindClass 昂貴,靜態快取
  4. 考慮 JNA / JNR / Panama (JEP 442):減少手寫 JNI 胶水代碼

常見坑與排查

  • UnsatisfiedLinkError:庫名錯誤(lib 前綴、.so/.dll 後綴)、路徑錯誤、架構不匹配(32/64 位)、依賴庫缺失
  • JNI_OnLoad 未實現導致版本不匹配
  • 記憶體洩漏:NewGlobalRef 未 DeleteGlobalRef
  • 線程附著:非 Java 建立的線程調用 JNI 需 AttachCurrentThread
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved