一步一步学习JNI(二)

       在一步一步学习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本地类型大小
bytejbtye1
shortjshort2
intjint4
longjlong8
floatjfloat4
doublejdouble8
charjchar2
booleanjboolean1
voidvoid

       以上的本地类型都定义在JAVA_HOME/include/jni.h与JAVA_HOME/include/platform/jni_md.h中。

引用类型

       同时Java本地类型也提供了另外三种类型,分别对应于Java类,对象与字符串三种引用的数据类型,在前面我们已经见过类,与对象的数据类型了。

Java引用类型Java本地类型
jclass
对象jobject
Stringjstring

       我们在看看官方给出引用类型,对应关系如下:

这里写图片描述

       有兴趣的同学可以去官方文档看看全部的类型。链接地址

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编译器在运行本地库是能够找到对应的方法,但是还有另外一种方式可以生成映射关系那就是注册本地函数,后面会讲到这种方式,其次就是讲解了函数类型的对应关系。更多的东西可以查看官方文档

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值