我目前遇到的跨语言交互一般都是通过函数指针(函数地址)。语言之间通过调用规定的API注册函数指针,由OS、VM或其它实例维护一个表格。JNI也不例外。
Java类调用一个native函数首先需要在Java类中声明它(不用实现),关键字native用来告诉javac编译时关于此函数的调用生成特殊的二进制码,再由VM去执行调用native函数。
一般在调用native的函数Java类中有个static块来加载对应的库,如下:
static
{
System.loadLibrary("xxx");
}
Java是跨平台的,它的VM当然知道如何处理类似Windows dll和Linux so之间的问题。
那么Java VM调用native函数都做了哪些工作呢?它在System.loadLibrary()时会调用lib(dll、so等)中的JNI_OnLoad(),lib应该在其中设置好前面说的VM维护的一张表格。下面是表格中的项的定义:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
name为函数名,signature为函数原型的描述,fnPtr为函数指针。
设置表格需要先声明一个static的JNINativeMethod数组:
static JNINativeMethod gMethods[] = {
// 见JNINativeMethod的定义
}
然后调用AndroidRuntime::registerNativeMethods(这是Android中VM的规范,Java VM也应该有类似的API):
AndroidRuntime::registerNativeMethods(env, "com/media/ffmpeg/FFMpeg", gMethods, sizeof(gMethods) / sizeof(gMethods[0]);
其中“com/media/ffmpeg/FFMpeg”说明了这是注册com.media.ffmpeg包下的FFMpeg Java类中的native函数,可以说是作用域吧,Java VM应该会根据这个来查第一层表。
但一般并没有在JNI_OnLoad()中直接调用上面的API注册native函数,因为不同Java类可能会有它自己的native函数的调用,因此一般都是做了一层封装,如JNI_OnLoad()中:
if(register_android_media_FFMpeg(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpeg");
goto end;
}
register_android_media_FFMpegAVFormatContext其实是对上面AndroidRuntime::registerNativeMethods封装,但它很好的分离的不同的Java类各自的native函数注册。
参考资料: