参考:《JAVA核心技术 卷II:高级特性》第12章 本地方法
Ubuntu下JNI的简单使用:
######################################################3
使用本地代码的三个理由:
1.你的应用需要访问系统的各个特性和设备。这些特性和设备通过Java平台是无法访问的;
2.你已经有了大量的测试过和调试过的用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上;
3.通过基准测试,你已经发现所编写的Java代码比用其他语言编写的等价代码要慢得多。
Java平台有一个用于和本地C代码进行互操作的API,称为Java本地接口(JNI)
note:可以使用C++代替C来编写本地方法。好处1:类型检查会更严格一些;好处2:访问JNI函数会更便捷一些。缺点:JNI并不支持Java类和C++类之间的任何映射机制
######################################################
为了实现本地代码,需要编写一个相应的C函数,必须完全按照Java运行环境预期的那样来命名这个函数。其规则是:
1.使用完整的Java方法名,比如:HelloWorld.print。如果类属于某个包,那么在前面添加包名,比如:com.zj.HelloWorld.print。
2.用下划线替换掉所有的句号,并加上Java_前缀,例如,Java_HelloWorld_print或Java_com_HelloWorld_print。
3.如果类名含有非ASCII字母或数字,比如:'_','$'或是大于'\u007F'的Unicode字符,用_0xxxx来替代它们,xxxx是该字符的值的4个十六进制数。
note:如果重载本地方法,也就是说,用相同的名字提供多个本地方法,那么必须在名称后附加两个下划线,后面再加上已编码的参数类型。例如,如果有两个本地方法print和print(int repeat),那么第一个成为Java_HelloWorld_print__,第二个称为Java_HelloWorld_print__I
######################################################33
"Hello World"的实现
Java编程语言使用关键字native表示本地方法,并且,你还需要在类中放置一个方法。
关键字native提醒编译器该方法将在外部定义。并且,本地方法不包含Java编程语言的代码,而且方法标题后直接跟着一个表示终结的分好。
HelloWorld.java:
class HelloWorld {
static {
System.loadLibrary("HelloWorld");
}
private native void greeting();
public static void main(String[] args) {
new HelloWorld().greeting();
}
}
注意,本地方法既可以是静态的也可以是非静态的。
在程序中添加一个对System.loadLibrary()方法的调用。为了确保虚拟机在第一次使用该类之前就会转载这个库,需要使用静态初始化代码块。
void loadLibrary(String libname)
装载指定名字的库,该库位于库搜索路径中。定位该库的确切方法依赖于操作系统
编译这个类时虚拟机会告诉你他不知道如何找到print函数,它会报告一个UnsatifiedLinkErro异常。
运行javah程序,它能够自动生成函数名。
要使用javah,需要编译HelloWorld.java源文件
javac HelloWorld.java
调用javah程序,从该类文件中产生一个C的头文件。javah可执行文件可以在jdk/bin目录下找到。可以用javah的名字来调用它,就像调用Java编译器一样
javah HelloWorld
这条命令会产生一个头文件HelloNative.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: greeting
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_greeting
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
如上所示,这个文件包含了函数Java_HelloNative_greeting的声明(字符串JNIEXPORT和JNICALL是在头文件jni.h中定义的。它们为那些来自动态装载库的导出函数标明了依赖于编译器的说明符)
将函数原型从头文件中复制到源文件中,并且给出函数的实现代码
/**
* @time 2015-10-7
* @author zj
**/
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_greeting
(JNIEnv *env, jobject obj) {
printf("Hello World!");
}
note:你可以使用C++实现本地方法。然而,必须将实现本地方法的函数声明位extern "C" (阻止C++编译器生产C++特有的代码)。例如:
/**
* @time 2015-10-7
* @author zj
**/
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_greeting
(JNIEnv *, jobject) {
printf("Hello World!");
}
将本地C代码编译到一个动态转载库中。
在Linux下的Gnu C编译器,使用如下命令:
gcc -fPIC -I /usr/lib/jvm/jdk/include -I /usr/lib/jvm/jdk/include/linux -shared -o libHelloWorld.so HelloWorld.c
如果运行的是Linux,必须把当前目录添加到库路径中。实现方式可以是通过设置LD_LIBRARY_PATH环境变量:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
或者是设置java.library.path系统属性:
java -Djava.library.path=. HelloWorld
总结:遵循以下步骤可以将一个本地方法链接到Java程序中
1.在Java类中声明一个本地方法
2.运行javah以获得包含该方法的C声明的头文件
3.用C实现该本地方法
4.将代码置于共享库中
5.在Java程序中加载该类库
note:一些本地代码的共享库必须先运行初始化代码。可以把初始化代码放到JNI_OnLoad方法中。类似地,如果提供该方法,当虚拟机关闭时,将会调用JNI_OnUnload方法。
其原型为:
jint JNI_OnLoad(JavaVM* vm, void* reserved);
void JNI_OnUnload(JavaVM* vm, void* reserved);
JNI_OnLoad方法要返回所需的虚拟机的最低版本,例如:
JNI_VERSION_1.2