HAL/JNI简明笔记(三)——java如何识别native方法的实现
2015-03-26 13:36
1666 查看
在文章HAL/JNI简明笔记(二)——基于stub架构的HAL实例,我们看见java调用jni导出接口是通过System.loadLibrary加载jni库,再声明下native即可,那么实现jni库的代码需要按照什么规则才能被JVM识别呢?
方法一,规范JNI函数名
方法二,通过jniRegisterNativeMethods来注册
不管用哪个方法,最终的目的就是在JVM中形成C函数和java方法的映射。
方法一的例子如下:
JNI->jni库
com_seuic_scanner_scanled.c
被Android.mk生成库libScannerled.so,放在/system/lib下他确实是库,不是stub,借尸so的stub一般放在/system/lib/hw下.
这个文件只导出了一个函数ScanLed_JNISetScanled,但函数名为什么写的那么复杂呢?
JVM是按照一定的命名规则来搜索导出的native函数的,这个规则就是函数命名规则:Java_包名_类名_方法名,其中包名中的点也用下划线'_'替代。上例中解释为com.seuic.scanner包中ScanLed类的方法为JNISetScanled,这个方法JNISetScanled也是java中使用的名字,这样JVM以后就一直用这样的函数映射。JNIEnv*env和jobject
obj两个参数是必须要有的。
那么JNIEXPORT和JNICALL是干什么的呢?
其实是不同平台之间的兼容性需求,他们中间的jint是返回值类型。
JAVA_HOME下include/x/jni.h和include/x/jni_md.h文件中的内容(x stands for win32 or linux,or any
other OS name),都是一些简单的宏定义。关于一般上面代码一些陌生的东西的定义在Java安装路径里的include中,Windows系统上是win32文件夹;Linux系统上是linux文件夹。
在win32/jni_md.h文件里
在linux/jni_md.h文件里
如上可见在linux环境就是空定义的,所以在linux下偏驱动的人就不用关心了,即使去掉JNIEXPORT和JNICALL也不影响;如果你是在windows(一般为偏apps的开发人员)下开发就加上吧,为了保持良好的兼容性都加上比较稳妥。
说明:层次简单代码量减少,它的编程思路一般是先在java中定义好方法,然后利用javah把带有native声明的函数生成.h头文件,再去实现c/c++文件,这个方法一般来自于apps层次的人员用,当然既然你都知道命名规则了,你直接先写c文件,再去写java文件也可以,当代码量大的时候,名字会让你头疼。
方法二例子:
JNI->jni库->so形式的stub
见HAL/JNI简明笔记(二)——基于stub架构的HAL实例,我仅贴出jni代码部分
关于映射表的输入输出参数表示,详细见oracle网站说明
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
JNI中参数一般分为基本类型和引用类型
其中引用类型的signature表示方法为Lfully-qualified-class;,注意最后有个分号,数组的signature表示方法为[type。另外特别注意jchar不是C中的char了,是16bits无符号,一般给unicode用的,而对应于char的脚jbyte.
引用类型tree,
ref: xyang0917 JNI/NDK开发指南专题
方法一,规范JNI函数名
方法二,通过jniRegisterNativeMethods来注册
不管用哪个方法,最终的目的就是在JVM中形成C函数和java方法的映射。
方法一的例子如下:
JNI->jni库
com_seuic_scanner_scanled.c
被Android.mk生成库libScannerled.so,放在/system/lib下他确实是库,不是stub,借尸so的stub一般放在/system/lib/hw下.
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <signal.h> #include <sys/time.h> #include <jni.h> #define SCANNER_LED_PATH "/sys/class/leds/scan/brightness" static int SetScannerLed(int brightness) { int fp; int size; char state = brightness?'1':'0'; fp = open(SCANNER_LED_PATH,O_WRONLY); if(fp == -1) return -1; size = write(fp,&state,1); if(size > 0){ close(fp); return 0; } close(fp); return 0; } void timer_handler(int msg) { switch(msg) { case SIGALRM: SetScannerLed(0); break; default: break; } } JNIEXPORT jint JNICALL Java_com_seuic_scanner_ScanLed_JNISetScanled(JNIEnv *env, jobject obj, jint brightness) { int ret = -1; struct itimerval nvalue; ret = SetScannerLed(brightness); if (ret < 0) return ret; signal(SIGALRM, timer_handler); nvalue.it_value.tv_sec = 0; nvalue.it_value.tv_usec = 100000;//100ms nvalue.it_interval.tv_sec = 0; nvalue.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &nvalue, NULL); return ret; }
这个文件只导出了一个函数ScanLed_JNISetScanled,但函数名为什么写的那么复杂呢?
JVM是按照一定的命名规则来搜索导出的native函数的,这个规则就是函数命名规则:Java_包名_类名_方法名,其中包名中的点也用下划线'_'替代。上例中解释为com.seuic.scanner包中ScanLed类的方法为JNISetScanled,这个方法JNISetScanled也是java中使用的名字,这样JVM以后就一直用这样的函数映射。JNIEnv*env和jobject
obj两个参数是必须要有的。
那么JNIEXPORT和JNICALL是干什么的呢?
其实是不同平台之间的兼容性需求,他们中间的jint是返回值类型。
JAVA_HOME下include/x/jni.h和include/x/jni_md.h文件中的内容(x stands for win32 or linux,or any
other OS name),都是一些简单的宏定义。关于一般上面代码一些陌生的东西的定义在Java安装路径里的include中,Windows系统上是win32文件夹;Linux系统上是linux文件夹。
在win32/jni_md.h文件里
#define JNIEXPORT __declspec(dllexport) #define JNIIMPORT __declspec(dllimport) #define JNICALL __stdcall
在linux/jni_md.h文件里
#define JNIEXPORT #define JNIIMPORT #define JNICALL
如上可见在linux环境就是空定义的,所以在linux下偏驱动的人就不用关心了,即使去掉JNIEXPORT和JNICALL也不影响;如果你是在windows(一般为偏apps的开发人员)下开发就加上吧,为了保持良好的兼容性都加上比较稳妥。
说明:层次简单代码量减少,它的编程思路一般是先在java中定义好方法,然后利用javah把带有native声明的函数生成.h头文件,再去实现c/c++文件,这个方法一般来自于apps层次的人员用,当然既然你都知道命名规则了,你直接先写c文件,再去写java文件也可以,当代码量大的时候,名字会让你头疼。
方法二例子:
JNI->jni库->so形式的stub
见HAL/JNI简明笔记(二)——基于stub架构的HAL实例,我仅贴出jni代码部分
namespace android { // These values must correspond with the Mode constants in // CTPService.java enum { MODE_GLOVE_OFF = 0, MODE_GLOVE_ON = 1, }; static ctp_device_t* get_device(hw_module_t* module, const char* name) { int err; hw_device_t* device; err = module->methods->open(module, name, &device); if (err == 0) { return (ctp_device_t*)device; } else { return NULL; } } /*return !NULL when OK,or NULL*/ static jint open(JNIEnv *env, jobject clazz) { int err; hw_module_t* module; ctp_device_t* dev = NULL; err = hw_get_module(CTP_HARDWARE_MODULE_ID, (hw_module_t const**)&module); if (err == 0) { dev = get_device(module, CTP_NAME); } return (jint)dev; } static void close(JNIEnv *env, jobject clazz, int ptr) { ctp_device_t* dev = (ctp_device_t*)ptr; if (dev) { dev->common.close((struct hw_device_t*)dev); } } static jstring GetCtpVer(JNIEnv *env, jobject clazz, int ptr) { char ver[15]; memset(ver, 0 , sizeof(char) * 15); ctp_device_t* dev = (ctp_device_t*)ptr; if (!dev) { return NULL; } dev->GetCtpVer(dev,(char *)ver); return env->NewStringUTF(ver); } static JNINativeMethod method_table[] ={ { "open", "()I", (void*)open }, { "close", "(I)V", (void*)close }, { "GetCtpVer", "(I)Ljava/lang/String;", (void*)GetCtpVer}, }; int register_CTPService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/seuic/touch/TouchService", method_table, NELEM(method_table)); } extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { ALOGE("GetEnv failed!"); return result; } ALOG_ASSERT(env, "Could not retrieve the env!"); register_CTPService(env); return JNI_VERSION_1_4; } }//end of namespace android方法二在你在java中加载库后,会自动去调用名为JNI_OnLoad( )的函数,这个函数返回JNI_VERSION_1_4是告诉JVM用JNI 1.4版本,不要用老版本。函数体中的register_CTPService(env)最终调用jniRegisterNativeMethods,这就是注册native到JVM,形成永久的java和c函数之间的映射关系,对比上列中参数一固定为env,参数二为注册到的com.secuic.touch包中的TouchService类中,参数三为注册的native方法映射表指针,参数四为映射表中个数。映射表每个元素的结构体定义在jni.h如下,
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;每个映射的参数一为java中用的方法名,参数二为字符串表示的输入输出参数类型,参数三为C中的函数名。
关于映射表的输入输出参数表示,详细见oracle网站说明
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
JNI中参数一般分为基本类型和引用类型
Java 类型 | Type Signature | 本地类型 | 描述 |
boolean | Z | jboolean | C/C++8位整型 |
byte | B | jbyte | C/C++带符号的8位整型 |
char | C | jchar | C/C++无符号的16位整型 |
short | S | jshort | C/C++带符号的16位整型 |
int | I | jint | C/C++带符号的32位整型 |
long | J | jlong | C/C++带符号的64位整型e |
float | F | jfloat | C/C++32位浮点型 |
double | D | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 | |
Class | Ljava/lang/Class; | jclass | Class对象 |
String | Ljava/lang/String; | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 | |
boolean[] | [Z | jbooleanArray | 布尔型数组 |
byte[] | [B | jbyteArray | 比特型数组 |
char[] | [C | jcharArray | 字符型数组 |
short[] | [S | jshortArray | 短整型数组 |
int[] | [I | jintArray | 整型数组 |
long[] | [J | jlongArray | 长整型数组 |
float[] | [F | jfloatArray | 浮点型数组 |
double[] | [D | jdoubleArray | 双浮点型数组 |
引用类型tree,
ref: xyang0917 JNI/NDK开发指南专题
相关文章推荐
- 如何在Android源码里查找Java中native方法对应的C++实现
- 如何查看Java native 方法的实现
- JAVA实现多线程的两种方法,及如何使用
- tomcat____批处理文件分析(坑 如何实现java方法定时?未解决)
- java如何实现人类识别技术
- 如何在Java中实现将两个数相互交换的方法
- Java学习之HashMap: 如何正确实现Map的entrySet()方法
- java中如何实现重复执行一个方法(事)
- React-native Android Java Module如何暴露自己的方法给js
- Java本地方法(native方法)的实现
- C#中如何实现JAVA中的String.replaceAll()方法功能
- Java里如何实现一个方法在不同情况下“返回”不同的类型变量?
- Java如何实现URL带请求参数(get/post)及得到get和post请求url和参数列表的方法
- 如何在Java中实现远程方法调用
- C#中如何实现JAVA中的String.replaceAll()方法功能
- Java中native方法(实现对类似C函数库的调用)
- java中,父类是抽象类不能通过工厂输出时,如何通过其他类实现输出的示例代码(工厂方法种类(1))
- JAVA中native方法调用C语言实现学习
- webView 实现 与 javascript调用java方法(也称js调用native 方法) helloworld