您的位置:首页 > 其它

JNI调用机制与JNI实现

2015-08-04 17:04 232 查看

JNI调用机制

JNI第一篇

此文是JNI的第二篇博客,我在之前的博客里写过如何实现一个基本的JNI,这篇文章是上一篇的升级版,详细解释了各种参数和实现方式,所以,在阅读此文前,请先看下如何实现一个基本的JNI调用,点击跳转

JNI:Java Native Interface是java本地接口。所谓的native,这里指C/C++写的底层接口。

一般在java层调用C层有这样的需求:

调用驱动,由于操作系统所提供的驱动一般都是C接口,Java语言本身不具备操作这些驱动的能力。


对于某些模块,Java的效率可能远低于C,因为,这些模块需要用C去完成。


对于某些功能模块,C层已经存在封装好的C代码,不想重复造轮子。


总之,基于某些原因,我们需要使用C代码。

Java访问C

Java中可以定义某个函数为native类型,对于native函数,只需要声明即可,因为该函数实现是native的,由相应的c去实现,Java编译器遇到native函数是,不会关心该函数的具体实现,因此,编译上不会出任何差错。

关于native函数的命名规则,看一下下面两个方法:


假设我们是在Framework层中的AssetManager中调用的,

java层方法

private native final void init();


C中对应方法

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz);


这种映射关系就是一种编程规范,C中方法名对应的是Java层的包名+方法名,方法中第一个参数env是一个指向java虚拟机运行环境,相当于jvm的管理者。通过它可以访问jvm内部的各种对象。第二个参数jobject是调用该函数的对象,上面的例子中,指代AssetManager对象。如果native声明中本身也有参数,那么这些参数会按顺序放在以上两个参数的后面,比如native中包含如下方法:

private native final String[] getArrayStringResource(int arrayRes);


其对应的C函数为:

static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,jint arrayResId);


在上面的转换关系中,我们注意到native中所使用的类型和Java中不同,比如java中的int,在native方法中变为了jint,返回值String[]变成了jobjectArray,这些具体的定义实际上是在jni.h中,该文件的存放路径为:

./dalvik/libnativehelper/include/nativehelper/jni.h


./external/webkit/webkit/android/JavaVM/jni.h


./ndk/build/platforms/android-n/arch-arm/usr/include/jni.h


./ndk/build/platforms/android-n/arch-x86/usr/include/jni.h


这里的android-n的n是对应的不同版本号。

最新版5.1的源码中,jni.h的位置在:

./libnativehelper/include/nativehelper/jni.h


./external/chromium_org/third_party/npapi/npspy/extern/java


./prebuilts/ndk/n/platforms/android-n/arch-arm/usr/include/jni.h


上面的第一个n是ndk版本,第二个n是Android版本。我们可以看出根据Android版本不同,位置并不是固定的,但是大致的位置是不变的,所以我们耐心搜索下就可以找到。

如果已经写好了本地的java代码,想生成头文件,那么我们不必自己按照规范来写,可以通过命令行来执行,这个在前面的博客里已经详细说明了,需要看的童鞋请在文章开始处点击跳转。

javah -d ~/Desktop -jni com.haiii.android.client.Foo


-d的含义是输出路径,-d必须在-jni前面,-jni的意思是产生jni头文件,后面的类名是Class文件的路径。调用javah,当前路径必须在该Class包名的根目录,生成的头文件默认名称为com_haiii_android_client_Foo.h,具体细节就不在这里复述了。

在java调用native之前,我们需要使用System.loadLibrary(“lib_name”)函数来装载该库。

值得注意的是,java中不能直接访问C中的变量,因为C中的变量对于Java来说都是私有的,如果想访问某个变量,需要让C提供一个get/set方法,以达到间接访问的目的。

C访问java

这个在博客里应该见得比较少了,因为很少会发生这样的需求,但是它绝对是有用的。

由于Java中的函数在native引擎中并没有直接的函数指针,Java函数只能由Java引擎去执行,而不是C,所以,C访问Java不能通过函数指针,而只能通过通用的参数接口,正如Java调用C一样。

C调用Java时,也需要把想要访问的类名,函数名,参数传递给Java引擎,步骤如下:

1.获取Java对象的类

cls = env->GetObjectClass(jobject);


其中env为Java调用C函数时的第一个参数,这意味着C调用Java函数只能在Java调用C函数中进行,否则无法获取env变量。换句话说,对于C来讲,就是”你不惹我,我不惹你“。jobject为第二个参数。cls的类型是jclass。

2.获取Java函数的id值

env->GetMethodId(cls,"method_name","([Ljava/lang/String;)V");


这个方法中第二个参数为Java中函数名称,第三个参数较为乱,它代表了Java函数的参数和返回类型,参数在()括号中,返回值在括号外。参数[Ljava/lang/String代表了String类型的参数,由于String本身是一个类,而不是Java的原子类型,所以前面加了包名,并用斜线分隔,最前面用一个中括号进行标识,后面用分号隔离。返回值V代表了void。GetMethodId中这种参数格式的定义大致如下:

boolean->Z;int->I;byte->B;long->L;char->C;double->D;short-S;float->F;Object->’L’+’packageName’+’;’这个就对应上面那个字符串

同样的,Java中提供了一个javap工具,这个工具可以让我们很清楚的找到Java函数的输入,返回参数,用法如下:

javap -s com/haiii/android/client/Foo


Foo.java的内容如下:

public class Foo {
    native void foo1();
    native int foo2(int a,String b);
}


使用javap命令,输出如下:

Compiled from "NativeClient.java"
public class com.sizon.testproject.NativeClient {
  public com.sizon.testproject.NativeClient();
    Signature: ()V

  native void foo1();
    Signature: ()V

  native int foo2(int, java.lang.String);
    Signature: (ILjava/lang/String;)I
}


这个是在cmd窗口输出的

可以清楚的看出各个函数对应的返回值和参数列表,这样我们就可以从容的写C端的调用了。




3.找到函数后,就可以调用该函数了

env->CallXXXMethod(jobject,mid,ret);


其中XXX代表了函数的返回值类型,具体包括Void、Object、Boolean、Byte、Char、Short、Int、Long、Float、Double。第一个参数是原来函数的第二个参数,即env后边的那个,第二个参数是我们获取的方法mid,第三个参数是我们自定义的存储返回值的变量。

通过上面的三步,我们就实现C中调用Java函数的目标。然后我们看一下,C中如何访问Java变量。

1.获取class,与前面相同

cls = env->GetObjectClass(jobject);


2.获取变量的id值

jfieldId fid = env->GetFiledId(cls,"filed_name","I");


参数filed_name为Java变量的名称,第三个参数为变量的类型,其格式与上面相同。

3.获取变量值

value = env->GetXXXField(env,jobject,fid);


该函数通过返回值的方式获取变量值,就是第三个参数,为返回值,前两个参数都是原装Java访问C函数的前两个参数。

到此,JNI的一些东西先讲到这里了。两篇文章结合看,对Android JNI调用这里应该就没有问题了。以后再说mk文件的事~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: