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 "<init>" as their name.

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

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

而对于引用类型,其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”及后面的“;”字符的。像下面这样:

看一些实际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执行的结果:

此外,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的写法,值得我们参考借鉴。

【转】:https://my.oschina.net/wolfcs/blog/126474

JNI,RegisterNative参数解析的更多相关文章

  1. 写个C#命令行参数解析的小工具

    最近测试工作做的比较多因此时常要创建一些控制台类型的应用程序.因为程序有不同的参数开关,需要在程序启动的时候通过命令行来给程序传递各种开关和参数.直接操作args有些不方便,所以就写了个解析参数的小工 ...

  2. Python--命令行参数解析Demo

    写没有操作界面的程序时,最讨厌的就是参数解析问题,尤其是很多参数那种,下面是一个小Demo,拿出来与各位分享: # -*- coding:utf8 -*- import os import datet ...

  3. Node基础:url查询参数解析之querystring

    模块概述 在nodejs中,提供了querystring这个模块,用来做url查询参数的解析,使用非常简单. 模块总共有四个方法,绝大部分时,我们只会用到 .parse(). .stringify() ...

  4. Zookeeper + Hadoop2.6 集群HA + spark1.6完整搭建与所有参数解析

    废话就不多说了,直接开始啦~ 安装环境变量: 使用linx下的解压软件,解压找到里面的install 或者 ls 运行这个进行安装 yum install gcc yum install gcc-c+ ...

  5. argparse - 命令行选项与参数解析(转)

    argparse - 命令行选项与参数解析(译)Mar 30, 2013 原文:argparse – Command line option and argument parsing 译者:young ...

  6. 一步一步自定义SpringMVC参数解析器

    随心所欲,自定义参数解析器绑定数据. 题图:from Zoommy 干货 SpringMVC解析器用于解析request请求参数并绑定数据到Controller的入参上. 自定义一个参数解析器需要实现 ...

  7. /proc/sys/ 下内核参数解析

    http://blog.itpub.net/15480802/viewspace-753819/ http://blog.itpub.net/15480802/viewspace-753757/ ht ...

  8. ThreadPoolExecutor参数解析

    ThreadPoolExecutor是一个非常重要的类,用来构建带有线程池的任务执行器,通过配置不同的参数来构造具有不同规格线程池的任务执行器. 写在前面的是: 线程池和任务执行器,线程池的定义比较直 ...

  9. Js把URL中的参数解析为一个对象

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title&g ...

随机推荐

  1. postman发送json请求

    简介: postman是一个很好的http模拟器,在测试rest服务时是很好用的工具,可以发送get.post.put等各种请求. 发送json的具体步骤: 1.选择post请求方式,同时将heade ...

  2. 二 random模块

    1 import random 2 3 print(random.random())#(0,1)----float 大于0且小于1之间的小数 4 5 print(random.randint(1,3) ...

  3. MDK生成.bin

    方法1: 默认选择编译输出的路径输出bin fromelf.exe --bin -o "$L@L.bin" "#L" 保存编译 方法2: 在要输出的目录下,新建 ...

  4. cookie与webStorage区别

  5. 浅谈CSRF

    CSRF是什么? (Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一,也被称为“One Click ...

  6. 高盛oa

    一道题根本不会,抄答案过了.一道自己写,莫名其妙出现了不会的bug.最后交了暴力解,过了5/7.估计要跪. 总结: 缺点:做过的不熟练.没做过的题不会.一个陌生的小bug也de不出来. 措施:多总结还 ...

  7. Runnable如何传参

    private class TimerUpdateTask implements Runnable{ private boolean isUnion = false; public TimerUpda ...

  8. android显示通知栏Notification以及自定义Notification的View

    遇到的最大的问题是监听不到用户清除通知栏的广播.所以是不能监听到的. 自定义通知栏的View,然后service运行时更改notification的信息. /** * Show a notificat ...

  9. Java(JFinal)实现sqlserver2017的数据库的备份与恢复

    1.连接数据库的代码: package com.once.xfd.dbutil; import java.sql.Connection; import java.sql.DriverManager; ...

  10. 使用SQL语句创建数据库2——创建多个数据库文件和多个日志文件

    在matser数据库下新建查询,输入的命令如下: USE master GOCREATE DATABASE E_MarketON PRIMARY--主文件组( NAME ='E_Market_data ...