java调用本地方法--jni简介

本篇结构:

  • 简介
  • JNI实现步骤
  • JNI实例--简单调用
  • 故障排除
  • CUDA 生成动态链接库 指令
  • 参考博文

一、简介

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。

有时可能存在需要访问的系统特性和设备通过java平台无法实现,又或者某个部分需要非常高的性能,这时可能就要牺牲跨平台的优势,一种解决方法就是寻求系统级的编程语言C/C++的帮助,这就需要用到JNI。

JNI是Java Native Interface的英文缩写,中文翻译为本地调用。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。

开发JNI程序会受到系统环境的限制,因为用C/C++语言写出来的代码或模块,编译过程当中要依赖当前操作系统环境所提供的一些库函数,并和本地库链接在一起。而且编译后生成的二进制代码只能在本地操作系统环境下运行,因为不同的操作系统环境,有自己的本地库和CPU指令集,而且各个平台对标准C/C++的规范和标准库函数实现方式也有所区别。这就造成使用了JNI接口的JAVA程序,不再像以前那样自由的跨平台。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。

二、JNI实现步骤

JNI的实现步骤如下:

  1. 编写java类,类中存在 natice 修饰的方法。
  2. 使用 javac 编译 java 类。
  3. 使用 javah 命令生成头文件。
  4. 使用C/C++实现本地方法,该方法定义可以在 javah 生成的头文件中找到。
  5. 生成动态链接库。
  6. 执行 java(java -Djava.library.path=XX XXX 或者拷贝动态库至 java.library.path 本地库搜索目录下)。

再来个详细点的英文版,就不翻译了:

  1. create a java project, create a java source file and write some code.
  2. declare native method, then load the linux native 'so' library.
  3. javac -d . XXX.java
  4. javah -d output-dir -jni -classpath xxx-dir package.path.ClassName
  5. create a c source file, write a method declared in the c file that generated by the command javah
  6. gcc -D_REENTRANT -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -c xxx.c
  7. gcc -shared xxx.o -o libxxx.so

三、JNI实例--简单调用

3.1、编写Java类

需要JNI实现的方法应当用native关键字声明,在类中,用System.loadLibrary()方法加载需要的动态链接库:

package jni.demo;

public class DemoJni {

    static {
        try {
            //调用动态链接库
            System.loadLibrary("sayHello");
        } catch (Exception e) {
            System.err.println("load native library [sayHello] failed.");
        }
        System.out.println("load native library [sayHello] succeed.");
    }

    // native关键字声明本地方法
    public native static String sayHello(String name);

    public static void main(String[] args) {
        System.out.println("Hello " + sayHello("Dean."));
    }
}

3.2、编译java类

javac -d . -encoding UTF-8 DemoJni.java

PS: -d 文件夹 解释:

设置类文件的目标文件夹。

假设某个类是一个包的组成部分,则 javac 将把该类文件放入反映包名的子文件夹中,必要时创建文件夹。比如,假设指定 -d c:\cp,而且该类名叫 com.package.MyClass,那么类文件就叫作c:\cp\com\package\MyClass.class。

若未指定 -d 选项,则 javac 将把类文件放到与源文件同样的文件夹中。

注意: -d 选项指定的文件夹不会被自己主动增加到用户类路径中。

3.3、生成相关JNI方法的头文件

javah -d jnilib -jni jni.demo.DemoJni

这个过程的实现一般是通过利用javah -jni * class生成的(-jni可以省略),也可以手工生成该文件;但是由于 Java 虚拟机是根据一定的命名规范完成对JNI方法的调用,所以手工编写头文件需要特别小心。

生成的头文件代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_demo_DemoJni */

#ifndef _Included_jni_demo_DemoJni
#define _Included_jni_demo_DemoJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_demo_DemoJni
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_jni_demo_DemoJni_sayHello
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

JNI函数名称(eg:Java_jni_demo_DemoJni_sayHello)分为三部分:首先是Java关键字,供Java虚拟机识别;然后是调用者类名称(全限定的类名,其中用下划线代替名称分隔符);最后是对应的方法名称,各段名称之间用下划线分割。

JNI函数的参数也由三部分组成:首先是JNIEnv *,是一个指向JNI运行环境的指针;第二个参数随本地方法是静态还是非静态而有所不同一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用;其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。

3.4、使用C/C++实现本地方法

#include "stdio.h"
#include "jni_demo_DemoJni.h"

JNIEXPORT jstring JNICALL Java_jni_demo_DemoJni_sayHello
  (JNIEnv *env, jclass jc, jstring name)
{
    return name;
}

在编码过程中,需要注意变量的长度问题,例如Java的整型变量长度为32位,而C语言为16位,所以要仔细核对变量类型映射表,防止在传值过程中出现问题。

本例中是String。

3.5、生成动态链接库

gcc -D_REENTRANT -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -c sayHello.c

gcc -shared sayHello.o -o libsayHello.so

-fPIC 作用于编译阶段,在编译动态库时(.so文件)告诉编译器产生与位置无关代码(Position-Independent Code),若未指定-fPIC选项编译.so文件,则在加载动态库时需进行重定向。

-c 对源代码进行预处理、编译、汇编,但不执行链接,产生的是源代码的目标文件(*.o)。

-l 用来指定程序要链接的库,-l参数紧接着就是库名。

-shared 生成共享目标文件。

3.6、运行java

java -Djava.library.path=jnilib jni.demo.DemoJni

不出意外,会输出:

load native library [sayHello] succeed.
Hello Dean.

四、故障排除

当使用 JNI 从Java 程序访问本机代码时,遇到的三个最常见的错误是:

  1. 无法找到动态链接。它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。
  2. 无法找到共享库文件。当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保JVM 可以访问该库文件。
  3. 无法找到具有指定说明的方法。确保C/C++ 函数实现拥有与头文件中的函数说明相同的说明。

五、CUDA 生成动态链接库 指令

nvcc -arch sm_20 --compiler-options '-fPIC' -o libkernel.so --shared kernel.cu -I./GPU_TLS -I $JAVA_HOME/include -I $JAVA_HOME/include/linux

六、参考博文

JNI/NDK开发指南(开山篇)
JNI中的内存管理
操作JNI函数以及复杂对象传递
JNI的数据类型和类型签名

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容