在一步一步学习JNI(一) 文章中我们对JNI进行了初步的入门,并且也给出了一个例子,并且在各个平台怎么生成动态链接库做了讲解。在这一篇中我们进行更深入的学习。
函数原型
我们在来看一下前面的样例代码。
package com.sunny;
public class Hello {
static{
//System.loadLibrary("Hello");
System.load("/Users/doc/Jni/jniHello.jnilib");
}
public static native int getSum(int a, int b);
public static void main(String[] args) {
// 虚拟机扫描加载的lib路径
System.out.println(System.getProperty("java.library.path"));
int sum = getSum(2, 5);
System.out.println(sum);
}
}
在上面的代码中我们声明了public static native int getSum(int a, int b);这样的一个函数,这里我们使用了native关键字,表示我们声明的是本地方法,该关键字是告诉java编译器,该函数在java代码中只是声明,具体的是有c/c++来进行实现。在运行的时候运行本地方法。
并且我们在也在静态代码块中加载了so包,当java编译器执行的时候,他就开始执行本地方法。我们在一步一步学习JNI(一)看到,如果没有加载so包或者函数找不到都会抛出如下的异常。
Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /Users/doc/Jni/jniHello.jnilib
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1827)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at com.sunny.Hello.<clinit>(Hello.java:7)
那么,Java虚拟机在加载本地运行库时,如果把java代码中的本地方法与库中的方法映射到一起的呐?这里就是使用了函数原型,当生成了函数原型,java虚拟机就会根据相应的规则将两个方法映射到一起。
那我们来看看函数原型。在前一篇文章中我们用javah生成了如下的代码。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_sunny_Hello */
#ifndef _Included_com_sunny_Hello
#define _Included_com_sunny_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_sunny_Hello
* Method: getSum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
我们来看看上面的代码,这里的代码不要随便修改,函数原型是在Java类中指定了native的方法基础上生成,我们可以对比的来看看两个函数。
//java
public static native int getSum(int a, int b);
//native
JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
(JNIEnv *, jclass, jint, jint);
由于只有一个函数,因此很多共性就不能体现了,这里我们定义两个native函数,并且生成他的函数原型。java代码如下:
public class Hello{
public static native String getName();
public native void setName(String name);
}
生成的.h文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: getName
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Hello_getName
(JNIEnv *, jclass);
/*
* Class: Hello
* Method: setName
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_setName
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
这里我们有两个函数,其中一个是静态函数,一个为非静态函数,再加上getSum,我们来看看这个三个函数的共同点。
三个函数都有JNIEXPORT,JNICALL两个关键字,这表示会被JNI调用,所有的native函数都有这个两个关键字,从上面我们也可以看到,三个函数都有,其实这两个关键字都是宏定义,它们被定义字jni_md.h 中,我们来看看Linux下的定义:
#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_
#define JNIEXPORT
#define JNIIMPORT
#define JNICALL
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif
typedef signed char jbyte;
#endif
这里可以看到在Linux下是定义是空定义,所以在Linux也可以不加这两个关键字。但是在其他平台下就必须要加。
我们来继续分析,可以看到生成的函数名都是JAVA_类名_方法名,并且所有函数都有JNIEnv *这个参数,但是可以发现第二个参数是不一样的,有的是jobject,有的是jclass,这其实是根据函数是否是静态来生成的,静态函数属于类,所有参数是jclass的,而非静态函数属于实例的,传递的是一个实例,参数为jobect。剩下的就是各个函数各自的参数。最后就是返回值,返回值处于JNIEXPORT, JNICALL中间。但是我们看到所有的参数都加了一个j,跟我们传入的参数不一样了。这里我继续来看看函数类型。
数据类型
本地类型
java是一种与平台无关的语言,数据类型在任何平台下都占用相同的大小,但是在JNI编程中,Java程序与c/c++程序中经常进行数据交换,因此必须要消除两个数据类型的差异,所以JNI提供了一套与java数据类型相对应的java本地类型,这样本地语言就可以使用java数据类型。我来看看他们的映射关系。
Java类型 | Java本地类型 | 大小 |
---|---|---|
byte | jbtye | 1 |
short | jshort | 2 |
int | jint | 4 |
long | jlong | 8 |
float | jfloat | 4 |
double | jdouble | 8 |
char | jchar | 2 |
boolean | jboolean | 1 |
void | void |
以上的本地类型都定义在JAVA_HOME/include/jni.h与JAVA_HOME/include/platform/jni_md.h中。
引用类型
同时Java本地类型也提供了另外三种类型,分别对应于Java类,对象与字符串三种引用的数据类型,在前面我们已经见过类,与对象的数据类型了。
Java引用类型 | Java本地类型 |
---|---|
类 | jclass |
对象 | jobject |
String | jstring |
我们在看看官方给出引用类型,对应关系如下:
有兴趣的同学可以去官方文档看看全部的类型。链接地址
jvalue类型
jvalue类型是一个union(联合),在JNI中将基本数据类型与引用类型定义在一个联合类型中,表示用jvalue定义的变量,可以存储任意JNI类型的数据,声明如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
总结
这里主要讲解了函数原型,这样可以保证java编译器在运行本地库是能够找到对应的方法,但是还有另外一种方式可以生成映射关系那就是注册本地函数,后面会讲到这种方式,其次就是讲解了函数类型的对应关系。更多的东西可以查看官方文档。