上俩篇,我们讲了都是jni基础的原理知识以及如何使用cmake实现简单的jni。接下来本想讲解Java与Native相互调用的,但是发现Java与Native相互调用中设计了好多基础知识,其中用的比较多的就是数据结构JNINativeMethod,所以,我想单独拿出来讲一下。
什么是JNINativeMethod
Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要区别是Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod。
JNINativeMethod 结构体的官方定义
JNINativeMethod存在这个文件中,结构体如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
下面就纤细介绍结构体参数的意思
- name
变量name是Java中函数的名字 - signature
变量signature,用字符串是描述了Java中函数的参数和返回值,这个参数最为复杂,接下来我会详细意义介绍。 - fnPtr
变量fnPtr是函数指针,指向native函数。前面都要接 (void *)
注意:
第一个变量name与第三个变量fnPtr是对应的,一个是java层方法名,对应着第三个参数的native方法名字
下面就详细介绍第二个变量signature,也是最重要的。
变量signature细解
在讲变量signature变量前,我们先讲一下jni常用的数据类型,我们知道java有自己的基本数据类型,但是java的数据类型是不能直接和c/c++交互的,为了统一这个问题,jni也 给出了一套数据类型的于Java一一对应。
- 常用类型
字符 | c/c++类型 | Java类型 |
---|---|---|
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
- 复杂类型(数组)
数组则以"["开始,用两个字符表示
名称 | c/c++类型 | Java类型 |
---|---|---|
[I | jintArray | int[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
[D | jdoubleArray | double[] |
[J | jlongArray | long[] |
[Z | jbooleanArray | boolean[] |
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";" 结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject.
举一个例子:Java是String类,其对应的类为jstring:
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 "(Ljava/lang/String;Landroid/os/Utils$UtilsStatus;)Z"
好了,到此,jni的数据类型也讲完了,下面回过头来说signature变量,这样就更加的易懂。顾名思义,signature就是签名的意思。因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。这样是很好,所以说JNI肯定要支持的,那JNI要怎么支持那,如果仅仅是根据函数名,没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——“签名”,即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。在接下来的一篇,我将会详细讲解签名,这里不过多讲解签名,只讲参数的用法,还是举个栗子说明:
/*
* 由于gMethods[]是一个<名称,函数指针>对照表,在程序执行时,
* 可多次调用registerNativeMethods()函数来更换本地函数的指针,
* 从而达到弹性调用本地函数的目的。
*/
static JNINativeMethod gMethods[] = {
{"setDataSource", "(Ljava/lang/String;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource},
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface},
{"prepare", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_prepare},
{"_start", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_start},
{"_stop", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_stop},
{"getVideoWidth", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth},
{"getVideoHeight", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight},
{"seekTo", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_seekTo},
{"_pause", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_pause},
{"isPlaying", "()Z", (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying},
{"getCurrentPosition", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition},
{"getDuration", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getDuration},
{"_release", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_release},
{"_reset", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_reset},
{"setAudioStreamType", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType},
{"native_init", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_init},
{"native_setup", "(Ljava/lang/Object;)V", (void *)com_media_ffmpeg_FFMpegPlayer_native_setup},
{"native_finalize", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize},
{"native_suspend_resume", "(Z)I", (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume},
};
比如上面列子中的这些参数:
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
括号里面表示参数的类型,括号后面表示返回值。
- “()”
“()” 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Fun(); - “(II)V”
表示 void Func(int, int);参数是俩个整型。 - “(Ljava/lang/String;Ljava/lang/String;)V”
表示 void Func(string, string);参数是俩个字符类型。
总结一下:
- 对象类型
以"L"开头,以";“结尾,中间是用”/" 隔开 - 数组类型
以"[“开始。如上表第2个(n维数组的话,则是前面多少个”[“而已,如”[[[D"表示“double[][][]”) - 对象数组类型
上述两者结合
JNINativeMethod 简单实战
下面是我简单的JNINativeMethod实战,代码如下:
#include "log_util.h"
#include <>
#include ""
#include <>
#include <>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/bnd/multimedialearning/jni/Sample";
//native对应得函数
static void printHello(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "native function is print to hello");
}
//获取所有的native函数数组
static JNINativeMethod getMethods[] = {
{"printHello", "(J)V", (void*)printHello},
};
//注册NativeMethods
static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGI("JNI","Registering %s natives\n", className);
clazz = (env)->FindClass( className);
if (clazz == NULL) {
LOGE("JNI","Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, getMethods, numMethods) < 0) {
LOGE("JNI","RegisterNatives failed for '%s'\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
return result;
}
//JNI_OnLoad动态注册:动态注册通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法
//注意:
//当我们使用()方法加载so库的时候,Java虚拟机就会找到这个JNI_OnLoad函数兵调用该函数,这个函数的作用是告诉Dalvik虚拟机此C库使用的是哪一个JNI版本,如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。
// 由于最新版本的JNI做了很多扩充,也优化了一些内容,如果需要使用JNI新版本的功能,就必须在JNI_OnLoad()函数声明JNI的版本。同时也可以在该函数中做一些初始化的动作,其实这个函数有点类似于Android中的Activity中的onCreate()方法。该函数前面也有三个关键字分别是JNIEXPORT,JNICALL,jint。
// 其中JNIEXPORT和JNICALL是两个宏定义,用于指定该函数时JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,jint就是用于衔接Java层和JNI层。
jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("JNI", "enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
//获取所有的函数的个数
jint size = sizeof(getMethods) / sizeof(JNINativeMethod);
LOGI("JNI", "jint size is '%s'\n", size);
jniRegisterNativeMethods(env, className, getMethods, size);
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
com_bnd_multimedialearning_jni_Sample_printHello
的函数原型为:
static jobject com_bnd_multimedialearning_jni_Sample_printHello(JNIEnv *env, jobject thiz, jstring path, jint baudrate);
注意:如果是C++,使用的是env, 如果是C,使用的是(*env),最好参考相应系统中的代码来写。
好了,关于结构体JNINativeMethod的讲述就告一段路了,接下来我将会讲解Java如何和c/c++相互调用,如果你对jni还没有入门,建议先看一下我的前倆篇博客。Android JNI(一)——NDK与JNI基础和Android JNI(二)——实战JNI之Hello World。