在文章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位浮点型 |
|
|
|
|
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开发指南专题