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

Java 與 C/C++ JNI 交互完整指南:從調用到數據傳遞

     編輯:Java 相關知識

JNI 核心流程

Java Native Interface (JNI) 實現 Java 與原生代碼(C/C++)雙向調用。標準流程:

  1. Java 聲明 native 方法
  2. javah 生成 C/C++ 頭文件
  3. 實現原生代碼,編譯為共享庫(.so/.dll/.dylib)
  4. Java 靜態塊 System.loadLibrary 加載庫
  5. 調用 native 方法

Java 調用 C++ 完整實戰

1. Java 類聲明

package com.example.jni;

public class NativeBridge {
    static {
        System.loadLibrary("nativebridge"); // 加載 libnativebridge.so
    }

    // 原生方法聲明
    public native int getInt();
    public native String getString();
    public native int computeSum(int a, int b);
    public native void callbackJava(String msg); // C++ 回調 Java
}

2. 生成頭文件

# 終端執行
cd project_root
javac -d build/classes src/com/example/jni/NativeBridge.java
javah -d jni/include -classpath build/classes com.example.jni.NativeBridge

生成 com_example_jni_NativeBridge.h:

/* DO NOT EDIT - machine generated */
#include 
#ifndef _Included_com_example_jni_NativeBridge
#define _Included_com_example_jni_NativeBridge
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_getInt
  (JNIEnv *, jclass);
JNIEXPORT jstring JNICALL Java_com_example_jni_NativeBridge_getString
  (JNIEnv *, jclass);
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_computeSum
  (JNIEnv *, jclass, jint, jint);
JNIEXPORT void JNICALL Java_com_example_jni_NativeBridge_callbackJava
  (JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif

3. C++ 實現

#include "com_example_jni_NativeBridge.h"
#include 

JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_getInt
  (JNIEnv *env, jclass clazz) {
    return 42;
}

JNIEXPORT jstring JNICALL Java_com_example_jni_NativeBridge_getString
  (JNIEnv *env, jclass clazz) {
    return env->NewStringUTF("Hello from C++");
}

JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_computeSum
  (JNIEnv *env, jclass clazz, jint a, jint b) {
    return a + b;
}

JNIEXPORT void JNICALL Java_com_example_jni_NativeBridge_callbackJava
  (JNIEnv *env, jclass clazz, jstring msg) {
    // 1. 獲取 Java 類引用
    jclass bridgeClass = env->FindClass("com/example/jni/NativeBridge");
    // 2. 獲取靜態方法 ID:public static void onNativeCallback(String)
    jmethodID mid = env->GetStaticMethodID(bridgeClass, "onNativeCallback", "(Ljava/lang/String;)V");
    // 3. 調用靜態方法
    env->CallStaticVoidMethod(bridgeClass, mid, msg);
    // 4. 釋放局部引用
    env->DeleteLocalRef(bridgeClass);
}

4. Android.mk / CMakeLists.txt 構建

# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := nativebridge
LOCAL_SRC_FILES := NativeBridge.cpp
include $(BUILD_SHARED_LIBRARY)

# CMakeLists.txt (現代 Android 推薦)
cmake_minimum_required(VERSION 3.10.2)
project("nativebridge")
add_library(nativebridge SHARED NativeBridge.cpp)
find_library(log-lib log)
target_link_libraries(nativebridge ${log-lib})

C++ 調用 Java 詳細步驟

核心 API:FindClass、GetMethodID/GetStaticMethodID、Call*Method、NewObject、NewStringUTF、GetStringUTFChars、ReleaseStringUTFChars、DeleteLocalRef。

關鍵區別:C vs C++ 風格調用

// C 風格(需顯式傳遞 JNIEnv*)
jclass cls = (*env)->FindClass(env, "java/lang/String");

// C++ 風格(隱式 this 指針)
jclass cls = env->FindClass("java/lang/String");

C++ 開發請始終使用 env->Method() 風格,代碼更簡潔、類型安全。

方法簽名獲取技巧

# 終端執行
javap -s -classpath build/classes com.example.jni.NativeBridge
# 輸出示例:
# public native int computeSum(int, int);
#   descriptor: (II)I
# public native void callbackJava(java.lang.String);
#   descriptor: (Ljava/lang/String;)V

簽名規則:B=byte, C=char, D=double, F=float, I=int, J=long, S=short, Z=boolean, L全限定類名;, [=數組。

JNI 數據類型映射表

Java 類型JNI 類型C/C++ 類型位寬
booleanjbooleanunsigned char8
bytejbytesigned char8
charjcharunsigned short16
shortjshortshort16
intjintint32
longjlonglong long64
floatjfloatfloat32
doublejdoubledouble64
Objectjobjectvoid*-
Stringjstringjobject-
Classjclassjobject-
Throwablejthrowablejobject-
int[]jintArrayjobject-

字符串傳遞最佳實踐

// Java -> C++: 獲取 UTF-8 指針,用完必須 Release
const char* nativeStr = env->GetStringUTFChars(javaStr, nullptr);
// 使用 nativeStr...
env->ReleaseStringUTFChars(javaStr, nativeStr);

// C++ -> Java: 創建新 String 對象
jstring javaStr = env->NewStringUTF("UTF-8 字符串");

// 注意:GetStringUTFChars 返回的是 Modified UTF-8,不支持補充平面字符(Emoji等)
// 需要完整 Unicode 時使用 GetStringChars (返回 jchar* UTF-16)

引用管理與內存安全

  • 局部引用:NewObject/FindClass/GetStringUTFChars 返回,生命周期綁定當前 native 方法調用,超出作用域自動釋放。手動 DeleteLocalRef 釋放大對象防止引用表溢出(默認 512)。
  • 全局引用:NewGlobalRef 跨調用持久化,必須 DeleteGlobalRef 釋放,否則內存洩漏。
  • 弱全局引用:NewWeakGlobalRef,不阻止 GC,使用前檢查 IsSameObject。

異常處理

jmethodID mid = env->GetMethodID(cls, "risky", "()V");
if (mid == nullptr) return; // 異常已拋出(NoSuchMethodError)
env->CallVoidMethod(obj, mid);
if (env->ExceptionCheck()) {
    env->ExceptionDescribe(); // 打印堆棧
    env->ExceptionClear();    // 清除異常,繼續執行或轉換為 C++ 異常
    // 可選:拋出 C++ 異常或返回錯誤碼
}

性能優化建議

  1. 緩存類與方法 ID:FindClass/GetMethodID 較慢,靜態緩存復用。
  2. 批量操作:GetIntArrayElements/ReleaseIntArrayElements 替代逐元素 Get/Set。
  3. 直接內存:NewDirectByteBuffer 共享 Java 堆外內存,零拷貝。
  4. 關鍵區:GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical 禁止 GC 移動數組,極短時間內使用。
  5. 避免頻繁 JNI 邊界調用:將邏輯下沉 C++ 批量處理,減少 Java↔C++ 切換。

常見崩潰排查

現象原因定位
UnsatisfiedLinkError庫未加載、方法簽名不匹配、架構不匹配(arm64 vs x86_64)logcat、nm -D lib.so 檢查導出符號
JNI ERROR: local reference table overflow循環中大量創建局部引用未釋放加 DeleteLocalRef、或用 Push/PopLocalFrame
SIGSEGV / EXC_BAD_ACCESS野指針、釋放後使用、數組越界AddressSanitizer、ndk-stack 解析 tombstone
ExceptionInInitializerError靜態塊中 loadLibrary 失敗檢查庫路徑、依賴庫、架構

現代化替代方案

  • JNA (Java Native Access):純 Java 調用動態庫,無需編寫 C++ 胶水代碼,開發效率高,性能略低於 JNI。
  • GraalVM Native Image:Java 直接編譯為原生可執行文件,消除 JNI 邊界。
  • Panama (Project Panama / JEP 442+):Foreign Function & Memory API,標準化、安全、高性能的原生互操作(JDK 21+ 預覽)。

總結

JNI 是 Java 與 C/C++ 橋樑,核心在於「簽名匹配、引用管理、異常傳遞、性能調優」。掌握 javah/javap -s 工作流、C++ 風格 API、局部/全局引用規則、字符串與數組傳遞細節,能構建高性能、穩定的混合應用。新項目優先評估 JNA 或 Panama 降低複雜度。

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