Register native method

Stella981
• 阅读 629

Register native method - 数据类型和method descriptor

使用JNI时,为了使得虚拟机可以找到在C/C++ code中定义的native方法,有两种机制可以用,一种是通过为native 方法以特定格式命名来实现,另外的一种是所谓的JNI_OnLoad机制。更多信息,可参考《android app中使用JNI》。在JNI_OnLoad机制中,我们需要创建一个映射表,以描述java code中声明的native 方法,与C/C++ code中的实现函数之间的对应关系。如下面的这样:

static JNINativeMethod gMethods[] = {
        NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"),
        NATIVE_METHOD(HelloJni, stringToJNI, "(Ljava/lang/String;)Ljava/lang/String;"),
        NATIVE_METHOD(HelloJni, sumIntWithNative, "([III)I"),
        NATIVE_METHOD(HelloJni, sumDoubleWithNative, "([DII)D"),
};

可以看到,这个映射表是一个数组,它的数据元素类型为JNINativeMethod,这个结构的定义如下:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

这个结构有三个成员,name为java code中声明的方法名,fnPtr为函数指针,指向实现了java 的native方法的那个C/C++函数,signature则为方法的签名。name 和fnPtr的含义都清晰明确。而方法的签名究竟是干什么的呢?看上面的**gMethods**的定义,想必我们大概也可以猜个八九不离十。没错,方法的签名就是用来描述一个方法,它所接受的参数类型,及其返回值类型的。

这个signature,又称为method descriptor。它的定义,究竟又有什么样的规则呢?JNI的Spec中对于这个问题,有如下的一段描述:

  • Method descriptors are formed by placing the field descriptors of all argument types in a pair of parentheses, and following that by the field descriptor of the return type. There are no spaces or other separator characters between the argument types. "V" is used to denote the void method return type. Constructors use "V" as their return type, and use "" as their name.

意思就是说,method descriptor由两个部分组成,一是由小括号包裹的,所有参数类型的field descriptors所组成的部分,其中的这些field descriptors对应于参数的顺序依次排列;二是紧紧跟着的返回值类型的field descriptors。整个的method descriptor中都不包含空格。“V”用于表示void 方法的返回值类型。构造函数用“V”作为他们的返回值类型,并使用“”作为他们的名字。而不接受任何参数的方法,其参数的field descriptors部分则为空,而只留小括号在那里。

接下来我们可以看一下,各种数据类型,它的field descriptors都是些什么鬼东西。首先,可以先来看一下Java的原始数据类型的field descriptors

Register native method

而对于引用类型,其field descriptor则是以“L”字符开头,后面紧跟着class descriptor,并以“**;”字符结尾。一个class descriptor用表示一个类或接口的名称。我们可以从一个类或接口的完全限定名中获取到这个class descriptor,方法为,将完全限定名字串中的“.”字符都用“/”字符来替换,比如java.lang.String** 这个类的class descriptor就是"java/lang/String"。数组当然也是引用类型。数组的field descriptor的构成则为“[”字符,后面紧跟着数组成员类型的field descriptor。比如,“int[]”的class descriptor为 "[I", “double[][][]” 的class descriptor为"[[[D"。回到field descriptor,原始数据类型的数组,它们的field descriptor不同于其他的引用类型的地方则在于,其是不需要开头的“L”及后面的“;”字符的。像下面这样:

Register native method

看一些实际code的例子。首先,是Java code中的native方法的声明:

public native int sumIntWithNative(int[] dataElement, int start, int end);
    public native double sumDoubleWithNative (double[] dataElement, int start, int end);
    public native String  stringToJNI(String text);

然后是native code中,我们所创建的映射表,值得我们重点关注的,是那些个方法的签名与Java code中native 方法的声明中参数及返回值类型之间的对应关系:

static JNINativeMethod gMethods[] = {
        NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"),
        NATIVE_METHOD(HelloJni, stringToJNI, "(Ljava/lang/String;)Ljava/lang/String;"),
        NATIVE_METHOD(HelloJni, sumIntWithNative, "([III)I"),
        NATIVE_METHOD(HelloJni, sumDoubleWithNative, "([DII)D"),
};

在Java code中,对于那些native方法的调用:

int[] dataElement = new int[] {
                2, 3, 4, 6
        };
        int sum = sumIntWithNative(dataElement, 0, dataElement.length);
        
        double[] doubleElement = new double[] {
                3.4, 5.3, 7.6, 9.2
        };
        double doubleSum = sumDoubleWithNative(doubleElement, 0, doubleElement.length);
        
        stringToJNI("text");

native code中,对于那些native方法的实现:

static jstring HelloJni_stringToJNI( JNIEnv* env, jobject thiz, jstring text)
{
    JniDebug("from HelloJni_stringToJNI");
    return text;
}


static jint HelloJni_sumIntWithNative( JNIEnv* env, jobject thiz,
        jintArray dataElement, jint start, jint end) {
    JniDebug("from HelloJni_sumIntWithNative");
}

static jdouble HelloJni_sumDoubleWithNative( JNIEnv* env, jobject thiz,
        jdoubleArray dataElement, jint start, jint end) {
    JniDebug("from HelloJni_sumDoubleWithNative");
}

在native 方法的实现中,值得我们关注的是,那些参数的声明。可以看到,Java Language Type用于java code中,native方法声明时描述参数或返回值的类型;field descriptor 用于method descriptor,或称signature中,描述参数或返回值的类型;而Native Type则用于native method 声明中,描述参数或返回值的类型。上面的那段code执行的结果:

Register native method

此外,android frameworks中JNI code的一些例子,也值得我们参考,如JellyBean/frameworks/base/core/jni/android/graphics/Canvas.cpp这个文件里面的映射表:

{"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
    {"drawPoint", "(FFLandroid/graphics/Paint;)V",
    (void*) SkCanvasGlue::drawPoint},
    {"drawPoints", "([FIILandroid/graphics/Paint;)V",
        (void*) SkCanvasGlue::drawPoints},
    {"drawLines", "([FIILandroid/graphics/Paint;)V",
        (void*) SkCanvasGlue::drawLines},
    {"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
    {"native_drawRect","(ILandroid/graphics/RectF;I)V",
        (void*) SkCanvasGlue::drawRect__RectFPaint},
    {"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
    {"native_drawOval","(ILandroid/graphics/RectF;I)V",
        (void*) SkCanvasGlue::drawOval},
    {"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
    {"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",
        (void*) SkCanvasGlue::drawArc},
    {"native_drawRoundRect","(ILandroid/graphics/RectF;FFI)V",
        (void*) SkCanvasGlue::drawRoundRect},
    {"native_drawPath","(III)V", (void*) SkCanvasGlue::drawPath},
    {"native_drawBitmap","(IIFFIIII)V",
        (void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/RectF;III)V",
        (void*) SkCanvasGlue::drawBitmapRF},
    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/Rect;III)V",

这个表中,非java 语言内部类的field descriptor,如android.graphics.Paint, android.graphics.RectF等的field descriptor的写法,值得我们参考借鉴。

Done.

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Souleigh ✨ Souleigh ✨
2年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Wesley13 Wesley13
2年前
JNI静态注册与动态注册详解
JNI注册,是指将java层方法(native关键字修饰的)和C层方法对应起来,以实现java层代码调用c层代码的目的。JNI注册分为静态注册和动态注册两种,静态注册是通过固定格式方法名进行关联,动态注册是通过动态添加映射关系来进行关联,方法名可以随便起,比较灵活,我们推荐使用动态注册。在进行注册前,需要先下载两个工具Clion和eclipse(能写java
Stella981 Stella981
2年前
JNI线程、Linux常用命令、权限、防火墙配置
JNI\_OnLoad:调用System.loadLibrary()函数时,内部就会去查找so中的JNI\_OnLoad函数,如果存在此函数则调用。JNI\_OnLoad会:告诉VM此native组件使用的JNI版本。​对应了Java版本,android中只支持JNI\_VERSION\
Wesley13 Wesley13
2年前
Java中System.loadLibrary() 的执行过程
_System.loadLibrary()_是我们在使用Java的JNI机制时,会用到的一个非常重要的函数,它的作用即是把实现了我们在Javacode中声明的native方法的那个libraryload进来,或者load其他什么动态连接库。算是处于好奇吧,我们可以看一下这个方法它的实现,即执行流程。(下面分析的那些code,来自于android4.2
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
Java日期时间API系列30
  实际使用中,经常需要使用不同精确度的Date,比如保留到天2020042300:00:00,保留到小时,保留到分钟,保留到秒等,常见的方法是通过格式化到指定精确度(比如:yyyyMMdd),然后再解析为Date。Java8中可以用更多的方法来实现这个需求,下面使用三种方法:使用Format方法、 使用Of方法和使用With方法,性能对比,使用
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这