上一节中 讲了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 <test.h>
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 , "<init>","()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;");
//获得该类型的构造函数 函数名为 <init> 返回类型必须为 void 即 V
jmethodID stu_costruct = env->GetMethodID(stu_cls , "<init>", "(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<String> by);
JNIEXPORT void JNICALL
Java_cn_com_syan_cysec_CysecJNI_test(JNIEnv *env, jobject instance, jobject by) {
// TODO
}
写到这里,好像都是千篇一律,不在多写了,只需掌握jni封装好的API就好,不熟悉的可以多查资料,做做测试练习就ok了。
本系列的第二部分,到这里也就结束了。大家主要掌握,如何调用c的函数库,如何传参就行,还有就是一些jni的转换,越用越熟练。