本篇结构:
- 简介
- 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的实现步骤如下:
- 编写java类,类中存在 natice 修饰的方法。
- 使用 javac 编译 java 类。
- 使用 javah 命令生成头文件。
- 使用C/C++实现本地方法,该方法定义可以在 javah 生成的头文件中找到。
- 生成动态链接库。
- 执行 java(java -Djava.library.path=XX XXX 或者拷贝动态库至 java.library.path 本地库搜索目录下)。
再来个详细点的英文版,就不翻译了:
- create a java project, create a java source file and write some code.
- declare native method, then load the linux native 'so' library.
- javac -d . XXX.java
- javah -d output-dir -jni -classpath xxx-dir package.path.ClassName
- create a c source file, write a method declared in the c file that generated by the command javah
- gcc -D_REENTRANT -fPIC -I
$JAVA_HOME
/include -I$JAVA_HOME
/include/linux -c xxx.c- 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 程序访问本机代码时,遇到的三个最常见的错误是:
- 无法找到动态链接。它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。
- 无法找到共享库文件。当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保JVM 可以访问该库文件。
- 无法找到具有指定说明的方法。确保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