您的位置:首页 > 编程语言 > Java开发

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下.

#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
双浮点型数组
其中引用类型的signature表示方法为Lfully-qualified-class;,注意最后有个分号,数组的signature表示方法为[type。另外特别注意jchar不是C中的char了,是16bits无符号,一般给unicode用的,而对应于char的脚jbyte.
引用类型tree,



ref: xyang0917 JNI/NDK开发指南专题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: