一 介绍
java调⽤c++,c++代码可以通过JNIEnv执行java代码。
安卓NDK 已经对JNI环境进行了集成,我们可以通过android studio来快速搭建一个项目。
二 项目搭建
打开android studio 创建工程,创建工程选择模板Native C++
三 模板格式介绍
生成的模板java类如下
public class MainActivity extends AppCompatActivity {
// Used to load the 'jnidemo' library on application startup.
static {
System.loadLibrary("jnidemo");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
}
/**
这个方法的实现在c++测
*/
public native String stringFromJNI();
}
native 标记的方法没有方法体,表示在c++测实现,
对应c++测的代码如下:
extern "C" 下⾯的⽅法采⽤C的编译⽅式
JNIEXPORT // JNIEXPORT 关键字 标记该⽅法可以被外部调⽤
jstring // 返回值类型 对应 java 测的 String
JNICALL // 关键字 jni call 约束函数⼊栈顺序,和堆栈内存清理规则
Java_com_example_jnidemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
那长串的名字
Java_com_example_jnidemo_MainActivity_stringFromJNI
红色为包名,绿色为类名,蓝色为方法名。
JNIEnv是c++ 和java沟通的桥梁,env就代表了java环境
四 签名和类型
你们肯定很懵,下面的表是什么,先不管它,请先硬着往下看
java类型对应在c++代码处的类型:
typedef void* jobject; /*对应任何Java对象,通常对应⼀个类的实例*
typedef jobject jclass; /*对应Java类对象,⽐如Student类*/
typedef jobject jstring; /*java.lang.String*/
typedef jobject jarray; /*数组*/
typedef jarray jobjectArray; /*任何对象的数组*/
typedef jarray jbooleanArray; /*boolean []*/
typedef jarray jbyteArray; /*byte []*/
typedef jarray jcharArray; /*char []*/
typedef jarray jshortArray; /*short []*/
typedef jarray jintArray; /*int []*/
typedef jarray jlongArray; /*long []*/
typedef jarray jfloatArray; /*float []*/
typedef jarray jdoubleArray; /*double []*/
typedef jobject jthrowable; /*java.lang.Throwable*/
typedef jobject jweak; /**/
五 函数调用
java 直接调用native 的方法,会执行c++处的实现,c++也可以通过env调用java处的代码。
c++侧执⾏java⽅法
hello,word
便于理解我java和c++ 代码放在了一块展示,请小白对比看
//java 处代码
public void hello(){
System.out.println("hello,word!");
}
public native void runHello();
// c++ 处代码
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_runHello(JNIEnv *env, jobject thiz)
// env是和java沟通的桥梁
// thiz代表 java对象this, 谁调用native,那么thiz就是那个对象的thiz
// 比如 A.runHello(),那么thiz就是A对象
// 获得Class 对象
jclass thisClass= env->GetObjectClass(thiz);
// 根据方法名字、参数、返回类型; 获得 hello ⽅法id,
// "hello" 为方法名字
// 后面的()V () 代表参数为空,V代表返回值为void
jmethodID helloMethodID= env->GetMethodID(thisClass,"hello", "()V");
// 根据方法id来执行方法,执行 thiz的hello方法
env->CallVoidMethod(thiz,helloMethodID);
}
c++执行java 测的有参函数
// java 处代码
private int add(int a,int b){
return a+b;
}
public native int runAdd(int a,int b);
// c++ 处代码
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_fuc_TestMain_runAdd(JNIEnv *env, jobject thiz, jint a,jint b){
// 获得Class 对象
// jclass 表示java Class 类型
jclass thisClass= env->GetObjectClass(thiz);
// 获取add ⽅法id
// (II)I 表示参数类型 第一个I表示第一个参数 类型为int类型,第二个I表示第二个参数为int类型,
// 括号外面那个I 表示返回类型为I即int
// 方法签名对应请看 上文第四节
jmethodID addMethodID= env->GetMethodID(thisClass,"add", "(II)I");
// 运⾏ add(a,b)
// jint 对应java int类型
jint result= env->CallIntMethod(thiz,addMethodID,a,b);
return result;
}
// test 处的测试方法
// 测试 add
@Test
public void testAdd(){
// 实际上运行c++处的实现,c++处的实现又去调用java的 add方法
int result = testMain.runAdd(4, 2);
System.out.println("addResult:"+result);
}
这么一看是不是非常像反射调用执行方法,我们对比一下反射学习:
// 反射执⾏
@Test
public void load() throws Exception{
// 获得class对象
Class<? extends TestMain> aClass = testMain.getClass();
// 获得add⽅法
Method add = aClass.getDeclaredMethod("add", int.class, int.class);
// 设置可⻅性
add.setAccessible(true);
// 运⾏add⽅法
Object invoke = add.invoke(testMain,2, 4);
System.out.println("执⾏结果:"+invoke);
}
是的,非常类似,都是先获得class对象,然后获得方法,再执行方法。
修改成员属性值
在c++处修改 java对象的成员变量
// java 处
public int age;
public int getAge() {
return age;
}
public native void setAge();
// c++ 处
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_setAge(JNIEnv *env, jobject thiz) {
// 获得class对象
jclass thisClass= env->GetObjectClass(thiz);
// 获得 属性ID
jfieldID ageID=env->GetFieldID(thisClass,"age", "I");
// 执⾏修改
env->SetIntField(thiz,ageID,35);
}
// test
@Test
public void testSetAge(){
System.out.println("修改前:"+testMain.getAge());
testMain.setAge();
System.out.println("修改后:"+testMain.getAge());
}
创建对象
在c++侧创建⼀个List数组,并向它添加"A","B"元素
// java
public native List<String> getStringArray();
// C++
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_myapplication_fuc_TestMain_getStringArray(JNIEnv *env, jobject){
// 获得ArrayList Class 对象
jclass listClass= env->FindClass("java/util/ArrayList");
// 获得ArrayList 构造⽅法id
// 构造方法签名统一为 <init>
jmethodID initMethodID= env->GetMethodID(listClass,"<init>", "()V");
// 创建 ArrayList 对象,相当于在java处那么 new了一个对象
jobject list= env->NewObject(listClass,initMethodID);
// 获得add ⽅法id
jmethodID addMethodID= env->GetMethodID(listClass,"add", "(ILjava/lang/Object;)V");
// 创建 A 和 B
jstring A= env->NewStringUTF("A");
jstring B= env->NewStringUTF("B");
// 添加元素
env->CallVoidMethod(list,addMethodID,0,A);
env->CallVoidMethod(list,addMethodID,1,B);
// 释放本地引⽤
// 只是释放c++ 测的引用,并没有删除 java那么对应的对象
env->DeleteLocalRef(A);
env->DeleteLocalRef(B);
return list;
}
// 在 c++ 的实现相当于执⾏了如下java 代码
ArrayList<Object> list = new ArrayList<>();
list.add(0,"A");
list.add(1,"B");
创建全局引⽤
1. 全局引⽤:全局引⽤在整个Java虚拟机中都有效,只要它们还在被使⽤,它们就不会被垃圾回收。全局引⽤在JNI代码结束时必须显式删除。
jobject globalRef = env->NewGlobalRef(localRef);// ...
env->DeleteGlobalRef(globalRef);
2. 本地引⽤:本地引⽤只在单个JNI调⽤期间有效。当JNI⽅法返回时,所有的本地引⽤都会⾃动被删除。(保险起⻅需要⼿动删除)
jobject localRef = env->NewLocalRef(tem);// ...
env->DeleteLocalRef(localRef);
异常处理
在JNI中,异常处理有点不⼀样,当在c++调⽤java⽅法抛出异常时候,函数不会直接捕获异常,异常会直接挂起,需要⼿动处理
• ExceptionCheck :这个⽅法⽤于检查是否有未处理的异常。它返回⼀个布尔值,如果存在未
处理的异常,则返回 true ,否则返回 false 。
• ExceptionOccurred :这个⽅法⽤于检查是否有异常发⽣。它返回⼀个指向异常对象的引⽤,
如果没有异常发⽣,则返回 nullptr 。
应该在执⾏⽅法时候都做⼀个异常处理,防⽌程序崩溃
// java
// 抛出运⾏异常
public int throwError(){
return 1/0;
}
public native void tryError();
// c++
// 抛出异常
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_tryError(JNIEnv *env, jobject thiz){
// 执⾏异常代码
jclass thisClass= env->GetObjectClass(thiz);
jmethodID throwErrorMethodId= env->GetMethodID(thisClass,"throwError","()I");
// 异常
jint result= env->CallIntMethod(thiz,throwErrorMethodId);
// 是否有异常
if(env->ExceptionOccurred()!= nullptr){
// 输出异常信息
env->ExceptionDescribe();
// 清除挂起的异常
env->ExceptionClear();
// 创建 RuntimeException 对象
jclass runErrorClass = env->FindClass("java/lang/RuntimeException");
// 抛出异常 消息"ERROR!"
env->ThrowNew(runErrorClass,"ERROR!");
return;
}
}
// 测试
@Test
public void testError(){
try {
testMain.tryError();
}catch (Exception e){
System.out.println("有异常");
e.printStackTrace();
}
}
六 总结
c++ 调⽤java⽅法
1、获得class对象
2、获得对象中的⽅法id
3、执⾏⽅法
c++创建java对象
1、获得class对象
2、获得构造⽅法id
3、执⾏构造⽅法
异常处理流程
c++层调⽤执⾏java对象⽅法
异常处理
如果认为对你有帮助,欢迎点赞收藏
如果认为对你有帮助,欢迎点赞收藏
如果认为对你有帮助,欢迎点赞收藏