android NDK开发之JNI操作第三方so

上一节中 讲了android NDK开发之JNI操作JAVA
本篇为android NDK开发的第二部分的第二节,调用三方so,这个so函数库应该是纯C/C++写的,非标准的jni形式,也就是java不能直接调用的,需要我们自己写jni函数,调用三方的so,再返回给java层。
那么,本节主要结合案例讲解一下,C/C++中的一些常见类型、函数参数、java数据类型的返回等。

1.导入第三方so

1.在CMakeLists.txt中添加如下内容:
#设置so资源路径
set(my_lib_path ${CMAKE_SOURCE_DIR}/libs)
# 添加三方的so库,注意不要带lib
add_library(fmod
            SHARED
            IMPORTED )
# 指名第三方库的绝对路径
set_target_properties( libfmod
                       PROPERTIES IMPORTED_LOCATION
                       ${my_lib_path}/${ANDROID_ABI}/libfmod.so )

当第三方库有针对不同架构编译了不同的库版本时,
有时候我们只需要引入我们想要的版本的库,当我们想要引入多个版本的库时,
可以使用ANDROID_ABI变量,它表示默认的ABI架构和NDK支持的架构,
如果我们在build.gradle中设置了过滤值,则表示过滤后的架构集合。

#在这里 把所有的so库 都设置上
target_link_libraries(
                        fmod
                       ${log-lib} )

这样就算导入成功了。有多个so,那就按照上面方式,写多个就行。

2.引入头文件

导入so库之后,我们还要在cpp文件中引入so的头文件,这样我们才能调用so里面的函数。

//在顶部引入so的头文件
#include 

2.调用so函数

我们先来看几种简单的c语言函数,然后根据这几种函数逐一讲解:

int test1();
char* test2(unsigned int index)
int test3(unsigned char* digest);
int test4(unsigned char **out, size_t *olen);
  • 第一种,无参数函数
  • 第二种,int类型参数,返回值为指针类型
  • 第三种,一级指针参数的函数
  • 第四种,二级指针参数的函数

我们以这几种讲解,以后遇到这种复杂类型的参数,举一反三,我们也会知道如何传参。

第一种函数:

针对这种函数,我们直接在JNI函数里调用即可,我的例子里返回值为int,这种基本数据类型JNI中对应着相应的类型为jint,所以JNI函数返回jint即可,java层就能接收到int类型的返回值,贴一下代码看的更直观:

//jni函数
extern "C"
JNIEXPORT jint JNICALL
Java_com_mmdet_jean_test(JNIEnv *env, jobject instance){
    return test1();//这里调用的是so里的函数
}
//对应的本地方法
public native int test1();
第二种函数:

我们看到,有一个int类型的参数,返回值为char,它相当于java的String,那我们就知道该如何调用如何返回了,但是char不能直接作为String类型返回,需要调用jni的API转换,看代码:

//jni函数
extern "C"
JNIEXPORT jstring JNICALL
Java_com_mmdet_jean_test(JNIEnv *env, jobject instance,jint index_){
  //调用so函数
  char* result = test2(index_);
  return env->NewStringUTF(result);
}
//对应的本地方法
public native String test2(int index);
第三种函数:

这个函数,参数类型为char,也就是string类型,那么native方法需要传递String类型的参数,返回值为int,不用多说了,因为String是引用类型,不能直接作为参数传入,也是需要调用jni的API转为c语言的char,好了,看代码:

//jni函数
extern "C"
JNIEXPORT jint JNICALL
Java_com_mmdet_jean_test3(JNIEnv *env, jobject instance,jstring digest_){
  //对jstring进行转换
  const char *digest = (*env)->GetStringUTFChars(env, digest_, 0);
  //调用so函数
  int result = test3(digest);
  return result ;
}
//对应的本地方法
public native int test3(String digest);
第四种函数:

第四种函数,带有一个二级指针的参数 char** out ,一个一级指针 size_t *olen,size_t 是c语言的一个类型,可以看做java的int或者long。
c语言中,指针的定义可以用&符号,如下:

int a = 5;
int *p = &a; //定义了一个int类型的指针,指针的实际指向为变量a

如上可见。二级指针,也就是定义了一个指针指向指针。

int a = 5;
int *p = &a;
int **p1 = &p; //二级指针,指向 *p

那么函数的二级参数也是这么传递,因为char* 可以表示string,那么char** 也就是String[],
test4()函数功能是输出数据out,因为前面传参我们已经知道了,所以我们这里就不传参了,如果你不知道数组怎么作为参数,传入JNI函数,不用担心,本篇的第三部分专门讲解java引用类型转为c可用的类型,以及c返回给java可用的类型。
下面我们先来调用此函数接收一个char **out返回byte[],,大家着重看怎么传参,如何返回,看代码:

//jni函数
extern "C"
JNIEXPORT byteArray JNICALL
Java_com_mmdet_jean_test4(JNIEnv *env, jobject instance){

  //定义一个变量,来接收输出的值
  char* out;
  size_t outlen = 0;
  //调用so函数
  //注意,这里传入的是&out和&outlen。函数test4经过运算后,对&out和&outlen进行赋值,我    们就能拿到结果了
  int result = test4(&out,&outlen );
 
// char* out可以作为字符串返回,可以转为byte[]返回
//char* out转为byte[]
  jbyteArray byteArray =(*env)->NewByteArray(env,outlen);
  (*env)->SetByteArrayRegion(env,byteArray, 0, outlen, (jbyte *) out);
  return byteArray ;
  //如上两步借助jni的API完成了char* 转字节数组,其中byteArray 对应java 的byte[]
}
//对应的本地方法
public native byte[] test4();

上述4个函数的实现就是jni函数中调用so里的函数的具体实现了,这里面会涉及一些c语言的知识,需要你了解一些。

通过这几个函数,你应该基本上知道,调用c函数,如何传参,如何返回,现在唯一的困难,就剩引用类型数据的互相转换了。不要担心,其实也很简单,因为jni已经给我们封装好了,我们只需要掌握这些API就行了。下面来看一些比较常用的互相转换吧。

3 复杂类型转换

java中的基本类型,直接使用对应的jni类型即可。对于引用类型,如object、数组、String,需要转换才能为c所用,当然,c函数返回的结果,如果是引用类型,也需要转换成对应的jni类型。

在Native层返回一个字符串
jstring str = env->newStringUTF("HelloJNI");  //直接使用该JNI构造一个jstring对象返回  
return str ;  
在Native层返回一个int型二维数组(inta[ ][ ])
    //获得一维数组 的类引用,即jintArray类型 
    jclass intArrayClass = env->FindClass("[I"); 
    //构造一个指向jintArray类一维数组的对象数组
    //该对象数组初始大小为dimion  ,dimion  自己指定
    jobjectArray obejctIntArray  =  env->NewObjectArray(dimion ,intArrayClass , NULL);  
    //构建dimion个一维数组,并且将其引用赋值给obejctIntArray对象数组  
    for( int i = 0 ; i< dimion  ; i++ )  
    {  
        //构建jint型一维数组  
        jintArray intArray = env->NewIntArray(dimion);  
        jint temp[10]  ;  //初始化一个容器,假设 dimion  < 10 ;  
        for( int j = 0 ; j < dimion ; j++)  
        {  
            temp[j] = i + j  ; //赋值  
        }  
        //设置jit型一维数组的值  
        env->SetIntArrayRegion(intArray, 0 , dimion ,temp);  
        //给object对象数组赋值,即保持对jint一维数组的引用  
        env->SetObjectArrayElement(obejctIntArray , i ,intArray);   
    }  
    return   obejctIntArray; //返回该对象数组 
在Native层返回一个byte型二维数组(byte[ ][ ])
JNIEXPORT jobjectArray JNICALL
Java_cn_com_syan_cysec_CysecJNI_generateRSAKeyPair(JNIEnv *env, jobject instance) {
//创建一个byte[][],往里面添加byte[]
 jclass m_strClass = (*env)->FindClass(env,"[B");
 jobjectArray result = (*env)->NewObjectArray(env,10, m_strClass,0);
 for( int i = 0 ; i< 10; i++ )  
  {  
    char* out = "hello";
    int arrayLen = 50;
    //创建byte[]
     jbyteArray jbyteArray_=(*env)->NewByteArray(env,arrayLen );
     (*env)->SetByteArrayRegion(env,jbyteArray_, 0, arrayLen , (jbyte *) out);
    //将数组添加到result (jobjectArray )
    (*env)->SetObjectArrayElement(env,result, 0, jbyteArray_);
   }
    return result;
}
Native层返回集合对象
package com.mmdet.jni;  
  
public class Student  
{  
    private int age ;  
    private String name ; 
    public Student(){ }  
    public Student(int age ,String name){  
        this.age = age ;  
        this.name = name ;  
    }     
}  
//////////////////////////
JNIEXPORT jobject JNICALL Java_com_feixun_jni_HelloJni_native_getListStudents  
  (JNIEnv * env, jobject obj)  
{  
    jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//获得ArrayList类引用  
    jmethodID list_costruct = env->GetMethodID(list_cls , "","()V"); //获得得构造函数Id  
    jobject list_obj = env->NewObject(list_cls , list_costruct); //创建一个Arraylist集合对象  

    //或得Arraylist类中的 add()方法ID,其方法原型为: boolean add(Object object) ;  
    jmethodID list_add  = env->GetMethodID(list_cls,"add","(Ljava/lang/Object;)Z"); 
  
    //获得Student类引用 
    jclass stu_cls = env->FindClass("Lcom/mmdet/jni/Student;"); 
    //获得该类型的构造函数  函数名为  返回类型必须为 void 即 V  
    jmethodID stu_costruct = env->GetMethodID(stu_cls , "", "(ILjava/lang/String;)V");  
  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        jstring str = env->NewStringUTF("Native");  
        //通过调用该对象的构造函数来new 一个 Student实例  
        jobject stu_obj = env->NewObject(stucls , stu_costruct , 10,str);  //构造一个对象  
        env->CallBooleanMethod(list_obj , list_add , stu_obj); //执行Arraylist类实例的add方法,添加一个stu对象  
    }  
    return list_obj ;  
}  
Java传递byte[]与byte[][]
public native void test(byte[] by);

JNIEXPORT void JNICALL
Java_cn_com_syan_cysec_CysecJNI_test(JNIEnv *env, jobject instance, jbyteArray by_) {
    jbyte *by = (*env)->GetByteArrayElements(env, by_, NULL);
    
    (*env)->ReleaseByteArrayElements(env, by_, by, 0);
}
-------------------
public native void test(byte[][] by);

JNIEXPORT void JNICALL
Java_cn_com_syan_cysec_CysecJNI_test(JNIEnv *env, jobject instance, jobjectArray by) {
//循环取出数组byte[]
}
Java传递list
public native void test(List by);

JNIEXPORT void JNICALL
Java_cn_com_syan_cysec_CysecJNI_test(JNIEnv *env, jobject instance, jobject by) {
    // TODO
}

写到这里,好像都是千篇一律,不在多写了,只需掌握jni封装好的API就好,不熟悉的可以多查资料,做做测试练习就ok了。

本系列的第二部分,到这里也就结束了。大家主要掌握,如何调用c的函数库,如何传参就行,还有就是一些jni的转换,越用越熟练。

你可能感兴趣的:(android NDK开发之JNI操作第三方so)