引言
拋開一堆概念,我們從本質出發。
java 調用c++
我們開發移動端或者后端服務 ,都是用的java 或者kotlin 語言。有時候我們需要用c++的一些庫(ocr識別/導航的算法/ 等)
因為一些跟硬件相關的接口或者系統的api 都是c++寫的。
C++調用Java
1、 回調/事件通知, 比如C++庫在處理過程中需要把結果、進度、異常等通知Java層(比如音視頻解碼完成后通知Java?UI刷新)
2、需要用Java層的功能,比如C++代碼需要用Java的網絡、數據庫、UI等功能,或者需要用Java寫的業務邏輯。
總結
只要C++需要通知Java、調用Java功能、或實現回調機制時,就會用C++調用Java。
實際開發中的配合
- 很多時候,兩者是配合使用的:
Java先調用C++做底層處理,C++處理完后再回調Java通知結果。
? ? 例如:Android音視頻播放器,Java層調用C++解碼,解碼完后C++回調Java刷新畫面。
簡要對比
場景 | Java調用C++ | C++調用Java |
用C++庫/高性能需求 | ? | |
需要底層/系統功能 | ? | |
需要回調/通知Java | ? | |
用Java功能/邏輯 | ? |
看這個表格就一目了然了。
好了再回到JNIEnv?。
正文
JNIEnv
?是 Java Native Interface (JNI) 中的一個重要結構,代表 JNI 環境的指針。它提供了一組函數,用于在本地代碼(C/C++)中與?Java 虛擬機(JVM)進行交互。
定義
JNIEnv
?是一個指向結構體的指針,包含了 JNI 函數的指針表。通過這個指針,開發者可以調用 JNI 提供的各種功能,如創建對象、調用方法、訪問字段等。
作用
- 訪問 Java 對象:?
JNIEnv
?允許本地代碼訪問 Java 對象的屬性和方法。 - 調用 Java 方法: 可以通過?
JNIEnv
?調用 Java 方法,包括靜態方法和實例方法。 - 處理異常:?
JNIEnv
?提供了處理 Java 異常的功能,可以檢查和拋出異常。
示例
c++調用 java
java
// Example.java
public class Example {public void greet() {System.out.println("Hello from Java!");}
}
c/c++
#include <jni.h>
#include <stdio.h>int main() {JavaVM *jvm; // Java 虛擬機JNIEnv *env; // JNI 環境JavaVMInitArgs vm_args; // JVM 啟動參數JavaVMOption options[1]; // JVM 選項// 設置 JVM 選項,指定類路徑options[0].optionString = "-Djava.class.path=."; // 當前目錄vm_args.version = JNI_VERSION_1_6; // JNI 版本vm_args.nOptions = 1; // 選項數量vm_args.options = options; // 選項數組vm_args.ignoreUnrecognized = 0; // 忽略未識別的選項// 創建 Java 虛擬機jint ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);if (ret != JNI_OK) {fprintf(stderr, "Failed to create JVM\\n");return 1;}// 查找 Example 類jclass cls = (*env)->FindClass(env, "Example");if (cls == NULL) {fprintf(stderr, "Failed to find Example class\\n");(*jvm)->DestroyJavaVM(jvm);return 1;}// 創建 Example 類的實例jobject obj = (*env)->AllocObject(env, cls);if (obj == NULL) {fprintf(stderr, "Failed to create Example object\\n");(*jvm)->DestroyJavaVM(jvm);return 1;}// 查找 greet 方法jmethodID mid = (*env)->GetMethodID(env, cls, "greet", "()V");if (mid == NULL) {fprintf(stderr, "Failed to find greet method\\n");(*jvm)->DestroyJavaVM(jvm);return 1;}// 調用 greet 方法(*env)->CallVoidMethod(env, obj, mid);// 銷毀 Java 虛擬機(*jvm)->DestroyJavaVM(jvm);return 0;
}
代碼解析
創建 JVM: ? 使用 JNI_CreateJavaVM 創建 Java 虛擬機,并獲取 JNIEnv 指針。
查找類和方法: ? ?使用 FindClass 和 GetMethodID 查找 Java 類和方法。
調用方法: ? ? ?使用 CallVoidMethod 調用 Java 方法。
java調用c++
1. Java?層聲明?native 方法
java
public class JniDemo {// 聲明 native 方法public native String stringFromJNI();static {// 加載 native 庫System.loadLibrary("myjni");}
}
2. 使用?javac?和?javah?生成頭文件(Android Studio?會自動生成)
如果你用 Android?Studio,可以直接寫 C/C++?代碼,不需要手動用?javah。
3. C/C++ 層實現
C 代碼(myjni.c):
#include <jni.h>JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_JniDemo_stringFromJNI(JNIEnv *env, jobject thiz) {return (*env)->NewStringUTF(env, "Hello from C!");
}
C++?代碼(myjni.cpp):
#include <jni.h>extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_JniDemo_stringFromJNI(JNIEnv *env, jobject thiz) {return env->NewStringUTF("Hello from C++!");
}
4. 配置?CMakeLists.txt 或 Android.mk
CMakeLists.txt 示例:
cmake_minimum_required(VERSION 3.4.1)
add_library(myjni SHARED myjni.c) # 或 myjni.cpp
find_library(log-lib log)
target_link_libraries(myjni ${log-lib})
5. 在?Java?層調用
JniDemo demo = new JniDemo();
String result = demo.stringFromJNI();
System.out.println(result); // 輸出: Hello from C!
6.?關鍵點說明
- Java 層用?native?關鍵字聲明方法。
- 用?System.loadLibrary("庫名")?加載?so 庫。
- C/C++ 層方法名格式:Java_包名_類名_方法名,下劃線分隔。
- Android?Studio?推薦用?CMake?管理?native?代碼。
概念總結
準確的說法:
- JNIEnv?實際上是一個指向結構體的指針(在?C?里是?JNIEnv*),它代表了本地線程(c/C++創建的線程)與?JVM?之間的接口。
- 通過?JNIEnv*,本地代碼可以調用大量 JNI 提供的函數(比如訪問?Java 對象、調用?Java?方法、拋出異常等)。
- 每個線程都有自己獨立的?JNIEnv*,不能跨線程使用。
簡化理解:
- JNIEnv*?是 JNI?提供給本地代碼與 JVM?交互的“橋梁”或“接口”。
這時候是不是懵了。。。。。。。。。。。。。。。。。。
“本地線程”
“本地線程”在?JNI(Java?Native?Interface)語境下,指的是運行本地(Native)代碼的線程,也就是運行?C/C++ 代碼的線程。
在?Android 或 Java 程序中,本地線程通常有兩種來源:
1. Java 線程
- 由 JVM 創建的線程(比如你在 Java 里 new Thread() 啟動的線程)。
- 這些線程天然和?JVM?關聯,JNI 方法會自動獲得對應的?JNIEnv*?指針。
2. 本地(Native)線程
- 由?C/C++ 代碼直接創建的線程(比如用?pthread_create?創建的線程)。
- 這些線程不是?JVM?創建的,默認和 JVM 沒有任何關聯。
- 如果本地線程需要訪問 JVM(比如調用 Java 方法),必須先通過?AttachCurrentThread?把自己“附加”到 JVM,獲得自己的?JNIEnv*?指針,使用完后再?DetachCurrentThread。
總結
- 本地線程:指運行在本地代碼(C/C++)中的線程,可能是 Java 啟動的,也可能是 C/C++ 啟動的。
- JNIEnv* 與本地線程:每個本地線程(無論來源)都要有自己的?JNIEnv*,不能跨線程使用。
例子
// C/C++ 里創建線程
void* thread_func(void* args) {JNIEnv* env;// 需要先 attach 到 JVM(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);// ... 使用 env 調用 Java 方法 ...(*jvm)->DetachCurrentThread(jvm);return NULL;
}
本地線程”與“Java線程”的區別
1. Java線程
- 定義:由?JVM(Java 虛擬機)通過?Java?代碼創建的線程,比如?new?Thread()?或?Executors.newSingleThreadExecutor()。
- 與?JVM 的關系:這些線程在創建時就已經和 JVM?關聯,JVM 會自動為它們分配?JNIEnv*。
- JNI 使用:在?Java?線程中調用 native 方法時,JNI 環境已經準備好,可以直接使用?JNIEnv*。
示例:
new Thread(() -> {// 這里是 Java 線程nativeMethod(); // 可以直接調用 JNI 方法
}).start();
2. 本地線程(Native?Thread)
- 定義:由本地代碼(C/C++)直接創建的線程,比如用?pthread_create?或?Windows?的?CreateThread。
- 與 JVM 的關系:這些線程不是?JVM 創建的,默認和 JVM?沒有關聯。
- JNI?使用:如果本地線程需要訪問?JVM(比如調用 Java 方法),必須先通過?AttachCurrentThread?將自己附加到?JVM,獲得?JNIEnv*,用完后要?DetachCurrentThread。
void* thread_func(void* args) {JNIEnv* env;// 需要手動 attach(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);// ... JNI 操作 ...(*jvm)->DetachCurrentThread(jvm);return NULL;
}// 創建本地線程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
3.?主要區別總結
對比項 | Java線程 | 本地線程(Native?Thread) |
---|---|---|
創建方式 | Java?代碼(JVM?創建) | C/C++?代碼(如?pthread_create) |
與?JVM?關系 | 自動關聯,自動有?JNIEnv* | 默認無關聯,需手動?attach |
JNI?使用 | 直接可用?JNIEnv* | 需?attach?后才有?JNIEnv* |
生命周期管理 | JVM?管理 | 需開發者手動管理 |
結論
- Java線程:JVM?創建,自動有?JNI?環境,直接用。
- 本地線程:C/C++ 創建,默認無?JNI?環境,需手動?attach/detach。
JNIEnv?實際上是一個指向結構體的指針(在?C?里是?JNIEnv*),它代表了本地線程(c/C++創建的線程)與?JVM?之間的接口。
這里為什么要說jvm?
這里說“JVM”是因為JNIEnv* 其實是本地代碼(無論是 Java?線程還是 C/C++ 線程)與 Java 虛擬機(JVM)之間的橋梁。
詳細解釋
1. JVM 的作用
- JVM(Java Virtual Machine)是運行 Java 程序的虛擬機,負責管理 Java 對象、內存、線程、類加載等。
- 所有 Java 代碼都運行在?JVM?之上,JVM?也負責管理和調度線程。
2. JNIEnv* 的作用
- JNIEnv* 提供了一組函數指針,允許本地代碼(C/C++)訪問和操作 JVM 里的 Java 對象、類、方法等。
- 你可以通過 JNIEnv* 調用 Java 方法、創建 Java 對象、拋出異常等,這些操作都需要?JVM 的支持。
3. 為什么強調“JVM”?
- 因為?JNIEnv* 不是單純讓你操作?Java 線程或?Java?對象,而是讓你通過 JNI?機制和 JVM 交互。
- 只有 JVM 能理解和管理 Java 世界的所有內容,JNIEnv* 就是本地代碼和 JVM 之間的“接口”或“橋梁”。
- 不管線程是?Java?創建的還是 C/C++ 創建的,只要你想操作?Java?世界,都必須通過?JVM,而?JNIEnv* 就是你和?JVM 之間的“鑰匙”。
總結
>?這里說“JVM”,是因為 JNIEnv* 讓本地代碼能夠和?Java 虛擬機(JVM)進行交互,訪問和操作 JVM 管理的所有 Java 資源。
好了,這里講的夠多了。先這樣,后面繼續講。
相關知識:
JNI實現Android音視頻播放器的設計方案-CSDN博客
c++ 的標準庫 --- std:: -CSDN博客