Java/Kotlin和C/C++的混编

Java和C/C++混编技术是一种常用的开发技术,目的是使用C/C++弥补Java在高性能计算方面的不足。如Android的Native开发,我们经常是使用Native代码来处理一些复杂的计算,比如图象处理。实现Java/Kotlin和C/C++通讯的接口叫Jni,下面我们来看一下Jni接口的基本用法。

1. Java/Kotlin调用C/C++方法

首先在Java/Kotlin中声明一个Native的空方法

package com.test.native;

public class Main {
  //Java: 使用native关键字声明
  public native String getString(int index);

  //Kotlin: 使用external关键字声明
  public external fun getString(index: Int): String
}

然后,在使用这个方法之前,如果你的C/C++库是动态的so库,那么必须提前在静态代码块中加载Native库,例如:

//Java: 在类的静态代码块中加载
static {
  System.loadlibrary("native-lib");
}

//Kotlin: 在伴生对象的初始化代码块中加载
companion object {
  init {
    System.loadlibrary("native-lib")
  }
}

参数名表示库的名称,这个"native-lib"完整的库文件名称是“libnative-lib.so”(为啥叫这个?这是Android Studio自动生成的,大家都懒得改名),libxxx.so中间这个xxx就是动态库的名称,这是C/C++通用的命名方式。

注意,一般情况下我们的程序一定有一个动态库作为主库,主库可以链接其他的库,然后Java/Kotlin代码中通过加载这个动态的主库来实现调用。

之后,我们需要在Native代码中写一个对应的Jni方法。在我们的native-lib.cpp中:

extern "C" {
JNIEXPORT jstring
JNICALL Java_com_test_native_Main_getString(JNIEnv * env, jobject obj, jint index) {
  return env->NewStringUTF(sprintf("from native index: %d", index));
}
}

这个方法有一系列的修饰符和一个很长很长的方法名,JNIEXPORT后边是返回值类型,JNICALL后边是方法的全名,名称第一个单词是Java(kotlin也得写Java,而不是用Kotlin开头),然后跟完整的包名和Java/Kotlin方法名,参数列表的前两个是默认传递的Java参数,包括Java环境和当前类的实例,之后才是真正需要的参数列表。Java中的类型都被映射成了jstring、jint等等,部分可以直接强转成C/C++类型,如jint,但是jstring不行,必须通过JNIEnv操作转换才能确保正确。

Java/Kotlin调用C/C++就这么简单几步,然后你就可以在JNI的方法里写你想用的功能了。

2. C/C++调用Java/Kotlin方法、属性

(1) 方法

这个相对麻烦一点,因为方法分了静态和非静态、有返回值和无返回值等等。

首先,有一个Java/Kotlin类,test.java:

package com.test.native;

class Main {
  int index;
  static String name;
  public String getString(int index );
  public static String getName();
}

然后再C/C++中调用这个方法:

jclass Main = env->FindClass("com/test/native/Main");
jmethodID main_constructor = env->GetMethodID(Main, "<init>", "()V");
jobject main = env->NewObject(Main, main_constructor);
jmethodID method = env->GetMethodID(Main, "getString", "(I)Ljava/lang/String;");
int index = 1;
env->CallObjectMethod(main, method, index);

主要过程分为三步:1,映射Main这个类,并且新建对象(也可以从Java传过来);2,找到需要调用的方法 getString;3,调用方法。

其中,类的分隔符使用 /,构造方法使用 <init>来代替。在获取方法的时候,最后一个参数叫做方法的签名,如 (I)Ljava/lang/String;。括号内是方法的参数,括号后边是方法的返回类型,I代表int类型, Ljava/lang/String代表String类型。完整的类型对应表:

类型 缩写
short S
int I
long J
float F
double D
boolean Z
byte B
char C
void V
对象类型 L包名,如:Ljava/lang/String
数组类型 前边加 [ 修饰符, 如: [S

对象参数或返回值后边必须要加分号,基本类型不用加,如 "(Ljava/lang/String;I)V","(IB)Ljava/lang/String;"。

静态方法调用

jclass Main = env->FindClass("com/test/native/Main");
jmethodID static_method = env->GetStaticMethodID(Main, "getName", "()Ljava/lang/String;");
env->CallStaticObjectMethod(Main, static_method);

流程类似,只是少了创建对象的过程。

(2) 属性

属性和方法比较类似,只是多了个修改的步骤:

jclass Main = env->FindClass("com/test/native/Main");
jmethodID main_constructor = env->GetMethodID(Main, "<init>", "()V");
//属性
jfieldID field = env->GetFieldID(Main, "index", "I");
jint index = env->GetIntField(main, field);
//更改属性值
env->SetIntField(main, field, 2);

//静态属性
jfieldID static_field = env->GetStaticFieldID(Main, "name", "Ljava/lang/String");
jobject name = env->GetStaticObjectField(Main, static_field);
//更改静态属性值
env->SetStaticObjectField(Main, static_field, env->NewStringUTF("main"));

代码看着多了点,其实原理并不复杂。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容