最近一直在做native这边的跨平台开发,整个结构基本就是下图:
大体说来就是,底层C/C++代码。那么对于两端分别有不同的处理:
由于我是做Android的,因此重点关注JNI,主要是总结应该知道的一些JNI知识。
Java是支持调用C/C++代码的,不过不能直接调用,因此需要一个中间层来进行转换、翻译,这就是JNI(Java Native Interface)的意思,JNI的作用就是粘合Java代码和C++代码。
JNI是Java特有的东西,是为了打通Java和C/C++代码的一种工具,因此其即不同于Java,又不同于C/C++。
我们知道,Java的数据类型分为基本数据类型和引用数据类型,JNI也是与之对应的。
基本数据类型的对应关系如下图:
对于八种基本数据类型,每种对应关系是xxx(Java类型)——>jxxx(JNI类型)
而对于引用类型,JNI的类型是jobject,继承的类型有多种,比如String、Class、数组、异常等,如下图:
可以看到,JNI的数据类型和Java的数据类型的对应关系是比较好理解和记忆的。
对于一个native的方法,比如说:
public native String sayHello();
得有一个JNI层的方法与之对应,这个对应规则是怎么样的呢?
这里简单来说有两种方式:
对于静态注册,JNI的方法命名规则为:
Java_packagename_classname_methodname(JNIEnv *env,jclass/jobject,...)
Java是提供了工具来生成JNI的头文件的,步骤是:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_xingfeng_HelloWorld */
#ifndef _Included_com_xingfeng_HelloWorld#define _Included_com_xingfeng_HelloWorld#ifdef __cplusplusextern "C" {#endif/* * Class: com_xingfeng_HelloWorld * Method: sayHello * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_xingfeng_HelloWorld_sayHello (JNIEnv *, jobject);
#ifdef __cplusplus}#endif#endif
可以看到方法签名是:
JNIEXPORT return_type JNICALL Java_packagename_classname_methodname(JNIEnv *,jobject,..)
这里是头文件,只是标识方法签名。
注意点:这里第二个参数是jobject类型,这是因为Java的sayHello()是一个对象方法,而如果是一个实例方法(static修饰的),这里第二个参数就会是jclass
这里只是头文件,下面建一个源文件,引入该头文件,实现方法的具体实现。
实现如下:
#include "com_xingfeng_HelloWorld.h"
extern "C" {
JNIEXPORT jstring JNICALL Java_com_xingfeng_HelloWorld_sayHello (JNIEnv *env, jobject thiz){ return env->NewStringUTF("Hello,This is from jni"); }}
这里,就是使用JNIEnv创建了一个字符串,并返回了。
动态注册的关键字是两个:
typedefstruct{constchar* name;constchar* signature;void* fnPtr;}JNINativeMethod;
#include <jni.h>#include <string.h>
jstring dynamic(JNIEnv *env,jobject thiz){ return env->NewStringUTF("Hello,this is from jni");}
//方法对应表const static JNINativeMethod methods[]={ {"dynamic","()Ljava/lang/String;",(void *)dynamic}};
extern "C"{ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){ JNIEnv *env=NULL; if(vm->GetEnv((void **) &env,JNI_VERSION_1_4)!=JNI_OK){ return JNI_FALSE; } jclass jclazz=env->FindClass("com/xingfeng/HelloWorld"); if(env-> RegisterNatives(jclazz,methods, sizeof(methods)/ sizeof(methods[0]))<0) { return JNI_FALSE; } return JNI_VERSION_1_4; }}
这里JNI_OnLoad里面的方法的模板是一样的,区别是FindClass()的参数,这里的参数就是那个类名,类名+方法名才能唯一确定一个接口。
注意点:FindClass()中的结构使用"/"来区别包名的,比如com.xingfeng.HelloWorld,在这里就要变成com/xingfeng/HelloWorld了
JNINativeMethod结构体中第二个参数对应于Java的方法签名,那么对于一个Java方法,其对应的方法签名是怎样的呢?
关于方法签名有几个要点:
基本类型的符号对应表如下图:
Z对应boolean,其他都是首字母
Java提供了方法可以看一个方法的签名的工具:javap。
对应于一个class文件,使用
javap -s classname
以上面的例子为例,执行上述命令,得到的结果如下:
Compiled from "HelloWorld.java"public class com.xingfeng.HelloWorld { public com.xingfeng.HelloWorld(); descriptor: ()V
public native java.lang.String sayHello(); descriptor: ()Ljava/lang/String;
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V
static {}; descriptor: ()V}
其中descriptor就是对应的签名,比如方法"sayHello"对应的方法签名就是"()Ljava/lang/String;"
区别是效率。静态注册,每次使用native方法时,都要去寻找;而动态注册,由于有张表的存在,因此查找效率高。
上面不管是静态注册方法,还是动态注册方法,都需要将cpp文件编译成平台所需要的库。不同平台,库的表现形式是不一样的,
由于我目前是使用的Mac,所以编译命令如下
gcc -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/include/darwin -fPIC ../jni/DynamicHelloWorld.cpp -o libdynamic.jnilib
gcc编译参数说明:
使用我想每个人应该都会的,这里就不赘述了。
上面主要是我自己从使用NDK开发中体会到的需要掌握的东西,最主要是静态注册与动态注册的实现。后面会介绍Java和JNI层如何互相作用,敬请期待。
本文分享自 每天学点Android知识 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!