引言
抛开一堆概念,我们从本质出发。
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
java
// Example.java
public class Example {
public void greet() {
System.out.println("Hello from Java!");
}
}
c/c++
cpp
#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
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):
cpp
#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):
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 示例:
cpp
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 层调用
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*,不能跨线程使用。
例子
cpp
// 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*。
示例:
java
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。
cpp
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 资源。
好了,这里讲的够多了。先这样,后面继续讲。
相关知识: